iOS 16 bug - ViewController 不响应点击

mon*_*c77 5 uiviewcontroller uiview ios swift ios16

编辑:根据评论者的要求,我们创建了一个小项目,您可以使用它来重现该问题。https://github.com/Coursicle/ReducingiOS16PopUpControllerBug。请注意,当您在 iOS 15 或更低版本上运行此项目时,弹出窗口会响应点击。在 iOS 16 上,它不响应任何点击。

我们有一个视图控制器,允许用户为我们的应用程序中的频道设置通知首选项。视图从屏幕底部向上滑动,并使用半透明背景视图使屏幕的其余部分变暗。在 iOS 15 及更低版本中,它按预期工作:您可以通过点击背景来关闭视图,或者选择一个新选项并点击“应用”。然而,在 iOS 16 中,视图和半透明背景视图对任何点击都没有响应。

最令人惊讶的是,我们有几乎相同的视图控制器(具有半透明背景视图的视图控制器,以弹出窗口的形式呈现UITabBarController等),它们在 iOS 15 和 iOS 16 中都可以正常工作。 iOS 16 中的工作视图和非工作视图之间似乎存在任何显着差异。

我们尝试过的事情:

  • 除了UITabBarController呈现弹出窗口之外还有其他视图控制器。
  • 设置 UIWindow sendEvent 断点,这表明点击击中了正确的元素,例如<UILabel: 0x133db3e10; frame = (78 0; 208 48); text = 'Every Post'; backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <_UILabelLayer: 0x60000233c0a0>
  • 直观地检查视图层次结构,这表明视图和背景位于最顶层,因此应该响应事件。这是图像
  • 为视图和背景视图上的各种元素显式设置isUserInteractionEnabled为。true

它看起来是这样的: 视觉示例

这是创建并显示弹出窗口的代码:

let rootController = UIApplication.shared.windows.first!.rootViewController as! CustomTabBar
let popover = NotificationSettingsPopUpController(customTabBarController: rootController, delegateController: self)
popover.presentationController?.delegate = self
popover.displayFilterPopUp()
Run Code Online (Sandbox Code Playgroud)

这是弹出窗口的代码:

class NotificationSettingsPopUpController: UIViewController {
    
    var customTabBarController : CustomTabBar
    let backgroundView : UIView = UIView()
    let filterPopUpViewSlideDuration = 0.2
    var filterPopUpViewHeight = CGFloat(0.52*Double(deviceScreenHeight))
    let filterPopUpViewWidth = CGFloat(deviceScreenWidth)
    let filterPopUpViewRadius = CGFloat(10.0)
    let filterOptionsContainer = UIStackView()
    
    // These variables will be used to handle the pan gesture
    // to drag the class filter view on and off screen.
    lazy var startingPosition = filterPopUpViewHeight*2.3
    lazy var finalPosition = filterPopUpViewHeight*1.5
    lazy var turningPointToShow = finalPosition*1.4
    lazy var turningPointToHide = finalPosition*1.2
    
    // Header Sizing Variables
    let headerLabel : UILabel = UILabel()
    var headerLabelTopMargin : CGFloat = 28
    let headerLabelSideMargin : CGFloat = 20
    
    // Header Sizing Variables
    let descriptionLabel : UILabel = UILabel()
    var descriptionLabelTopMargin : CGFloat = 45
    let descriptionLabelSideMargin : CGFloat = 40
    
    // Apply Button Variables
    let applyButton = UIButton()
    var applyButtonWidth: CGFloat = CGFloat(deviceScreenWidth)*0.85
    var applyButtonHeight: CGFloat = 45
    var applyButtonFontSize: CGFloat = 18
    let applyButtonColor = UIColor.init(hex: "#207af3")
    let applyButtonColorLight = UIColor.init(hex: "#3686f3")
    
    // Sort option buttons
    let everyPostOption : MenuOptionsView = MenuOptionsView()
    let topDailyOption : MenuOptionsView = MenuOptionsView()
    let topWeeklyOption : MenuOptionsView = MenuOptionsView()
    let topMonthlyOption : MenuOptionsView = MenuOptionsView()
    let neverOption : MenuOptionsView = MenuOptionsView()
    
    var currentlySelectedOption : String
    
    // Delegate Controller
    var delegateController : PostsInChannelViewController
    
    init(customTabBarController: CustomTabBar, delegateController: PostsInChannelViewController){
        
        // Set the initial sort option
        self.currentlySelectedOption = "Top Weekly Post"
        if let settings = getSettingsForChannel(delegateController.channel.id){
            if settings.keys.contains("notificationPreference"){
                self.currentlySelectedOption = settings["notificationPreference"] ?? "Top Weekly Post"
            }
        }
        
        // set delegate controller
        self.delegateController = delegateController
        
        // set viewToReturnTo so the correct view is displayed
        // once the class filter view is dismissed
        self.customTabBarController = customTabBarController
        super.init(nibName: nil, bundle: nil)
        
        // add class filter view and background to the window
        customTabBarController.view.addSubview(backgroundView)
        customTabBarController.view.addSubview(view)
        
        // display translucent black backdrop to hide classes view
        // when class filter view is being displayed
        backgroundView.backgroundColor = UIColor(hex: "#000000", alpha: 0)
        backgroundView.frame = CGRect(x: 0, y: -deviceScreenHeight, width: deviceScreenWidth, height: deviceScreenHeight)
        
        // adjust layout variables based on screen size
        if UIDevice().screenType == .iPhones_6_6s_7_8 || UIDevice().screenType == .iPhone_12Mini {
            filterPopUpViewHeight = CGFloat(0.55*Double(deviceScreenHeight))
            startingPosition = filterPopUpViewHeight*2.3
            finalPosition = filterPopUpViewHeight*1.5
            turningPointToShow = finalPosition*1.4
            turningPointToHide = finalPosition*1.2
        }
        
        if UIDevice().screenType == .iPhones_6_6s_7_8{
            filterPopUpViewHeight = CGFloat(0.65*Double(deviceScreenHeight))
        }
        
        if UIDevice().screenType == .iPhone_XSMax_ProMax || UIDevice().screenType == .iPhone_12ProMax{
            filterPopUpViewHeight = CGFloat(0.47*Double(deviceScreenHeight))
        }
        
        // position class filter view off-screen initially (tried to do this
        // with layout anchors but seemed more complicated than it was
        // worth - could try doing it again if we find it's necessary)
        view.frame = CGRect(x: 0, y: CGFloat(deviceScreenHeight), width: filterPopUpViewWidth, height: filterPopUpViewHeight)
        view.layer.cornerRadius = filterPopUpViewRadius
        view.backgroundColor = .white
    }
    
    // add gesture recognizers so that when user taps outside of
    // class filter view or swipes down, the class filter view is dismissed
    override func viewDidLoad() {
        
        backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissMyself)))
        
        // Set up the header label
        view.addSubview(headerLabel)
        headerLabel.text = "Notify me"
        headerLabel.font = .systemFont(ofSize: 24, weight: .bold)
        headerLabel.sizeToFit()
        if (UIDevice().screenType == .iPhone_XSMax_ProMax || UIDevice().screenType == .iPhones_X_XS_12MiniSimulator || UIDevice().screenType == .iPhone_XR_11) && iOSIsOld {
            headerLabelTopMargin += 15
        }
        headerLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            headerLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: headerLabelSideMargin),
            headerLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: headerLabelTopMargin),
        ])
        
        setUpFilterOptions()
        
        setUpApplyButton()
    }
    
    func setUpApplyButton() {
        // Create apply now button
        applyButton.translatesAutoresizingMaskIntoConstraints = false
        applyButton.layer.cornerRadius = applyButtonHeight/2
        
        if(UIDevice().screenType == .iPhones_5_5s_5c_SE){applyButtonFontSize -= 4}
        applyButton.titleLabel?.font = UIFont.systemFont(ofSize: applyButtonFontSize, weight: UIFont.Weight.regular)
        applyButton.setTitle("Apply", for: .normal)
        applyButton.setTitleColor(UIColor.white, for: .normal)
        applyButton.backgroundColor = applyButtonColor
        
        // Add gesture recognizer for the apply button
        applyButton.addTarget(self, action: #selector(applyButtonTapped), for: .touchUpInside)
        applyButton.addTarget(self, action: #selector(applyButtonTouchedDown), for: .touchDown)

        // Add apply button to the view and set position
        view.addSubview(applyButton)
        NSLayoutConstraint.activate([
            applyButton.topAnchor.constraint(equalTo: filterOptionsContainer.bottomAnchor, constant: 22),
            applyButton.widthAnchor.constraint(equalToConstant: applyButtonWidth),
            applyButton.heightAnchor.constraint(equalToConstant: applyButtonHeight),
            applyButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        ])
    }

    func setUpFilterOptions() {
        
        filterOptionsContainer.translatesAutoresizingMaskIntoConstraints = false
        filterOptionsContainer.axis  = NSLayoutConstraint.Axis.vertical
        filterOptionsContainer.distribution  = UIStackView.Distribution.fillEqually
        filterOptionsContainer.alignment = UIStackView.Alignment.center
        filterOptionsContainer.spacing = 5
        
        view.addSubview(filterOptionsContainer)
        
        filterOptionsContainer.addArrangedSubview(everyPostOption)
        filterOptionsContainer.addArrangedSubview(topDailyOption)
        filterOptionsContainer.addArrangedSubview(topWeeklyOption)
        filterOptionsContainer.addArrangedSubview(topMonthlyOption)
        filterOptionsContainer.addArrangedSubview(neverOption)
        
        everyPostOption.iconView.text = String.fontAwesomeIcon(name: .bell)
        topDailyOption.iconView.text = String.fontAwesomeIcon(name: .bell)
        topWeeklyOption.iconView.text = String.fontAwesomeIcon(name: .bell)
        topMonthlyOption.iconView.text = String.fontAwesomeIcon(name: .bell)
        neverOption.iconView.text = String.fontAwesomeIcon(name: .bell)
        
        everyPostOption.infoLabel.text = "Every Post"
        topDailyOption.infoLabel.text = "Top Daily Post"
        topWeeklyOption.infoLabel.text = "Top Weekly Post"
        topMonthlyOption.infoLabel.text = "Top Monthly Post"
        neverOption.infoLabel.text = "Never"
        
        everyPostOption.addTarget(self, action: #selector(selectNewSortOption(_:)), for: .touchUpInside)
        topDailyOption.addTarget(self, action: #selector(selectNewSortOption(_:)), for: .touchUpInside)
        topWeeklyOption.addTarget(self, action: #selector(selectNewSortOption(_:)), for: .touchUpInside)
        topMonthlyOption.addTarget(self, action: #selector(selectNewSortOption(_:)), for: .touchUpInside)
        neverOption.addTarget(self, action: #selector(selectNewSortOption(_:)), for: .touchUpInside)
        
        switch(currentlySelectedOption) {
        case "Every Post":
            everyPostOption.backgroundColor = UIColor.init(hex: "#F2F2F2")
            everyPostOption.checkmarkIconView.isHidden = false
        case "Top Daily Post":
            topDailyOption.backgroundColor = UIColor.init(hex: "#F2F2F2")
            topDailyOption.checkmarkIconView.isHidden = false
        case "Top Weekly Post":
            topWeeklyOption.backgroundColor = UIColor.init(hex: "#F2F2F2")
            topWeeklyOption.checkmarkIconView.isHidden = false
        case "Top Monthly Post":
            topMonthlyOption.backgroundColor = UIColor.init(hex: "#F2F2F2")
            topMonthlyOption.checkmarkIconView.isHidden = false
        case "Never":
            neverOption.backgroundColor = UIColor.init(hex: "#F2F2F2")
            neverOption.checkmarkIconView.isHidden = false
        default:
            assert(false)
            return
        }

        NSLayoutConstraint.activate([
            filterOptionsContainer.topAnchor.constraint(equalTo: headerLabel.bottomAnchor, constant: 20),
            filterOptionsContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        ])
    }
    
    
    @objc func applyButtonTapped(_ sender: UIButton) {
        
        // Store the preference in user defaults so that
        // we can display what setting they have next time they open it
        storeSettingsForChannel(settingName: "notificationPreference", settingValue: currentlySelectedOption, channel: delegateController.channel.id)
        
        // send the preference to the server
        if let uuid = getUUID(){
            setUserChannelNotificationPreference(uuid: uuid, channelID: delegateController.channel.id, preference: currentlySelectedOption)
        }
        
        // Dismiss Filter modal screen
        dismissMyself()
    }
    
    @objc func applyButtonTouchedDown(_ sender: UIButton){
        sender.backgroundColor = applyButtonColorLight
    }
    
    @objc func selectNewSortOption(_ sender: MenuOptionsView) {
        everyPostOption.backgroundColor = .white
        topDailyOption.backgroundColor = .white
        topWeeklyOption.backgroundColor = .white
        topMonthlyOption.backgroundColor = .white
        neverOption.backgroundColor = .white
    
        everyPostOption.checkmarkIconView.isHidden = true
        topDailyOption.checkmarkIconView.isHidden = true
        topWeeklyOption.checkmarkIconView.isHidden = true
        topMonthlyOption.checkmarkIconView.isHidden = true
        neverOption.checkmarkIconView.isHidden = true
        
        sender.backgroundColor = UIColor.init(hex: "#F2F2F2")
        sender.checkmarkIconView.isHidden = false
        
        currentlySelectedOption = sender.infoLabel.text ?? "Top Weekly Post"
    }
    
    // this function sets up and displays the class filter view -
    // this gets called from the home view controller
    func displayFilterPopUp() {
        backgroundView.frame = CGRect(x: 0, y: 0, width: deviceScreenWidth, height: deviceScreenHeight)
        // slide in class filter view with animation
        UIView.animate(withDuration: filterPopUpViewSlideDuration, delay: 0, options: .curveEaseOut, animations: {
            self.backgroundView.backgroundColor = UIColor(hex: "#000000", alpha: 0.5)
            self.view.frame = CGRect(x: 0, y: CGFloat(deviceScreenHeight)-self.filterPopUpViewHeight, width: self.filterPopUpViewWidth, height: self.filterPopUpViewHeight)
        })
    }
    
    // This function slides the class filter view off-screen and fades out the backgroundView
    override func dismissMyself() {
        UIView.animate(withDuration: filterPopUpViewSlideDuration, animations: {
            self.backgroundView.alpha = 0
            self.view.frame = CGRect(x: 0, y: CGFloat(deviceScreenHeight), width: self.filterPopUpViewWidth, height: self.filterPopUpViewHeight)
            
            self.delegateController.headerLabel.textColor = .black
            self.delegateController.headerCaret.textColor = .black
        }, completion : { finished in
            self.backgroundView.frame = CGRect(x: 0, y: deviceScreenHeight, width: deviceScreenWidth, height: deviceScreenHeight)
            self.view.removeFromSuperview()
        })
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
Run Code Online (Sandbox Code Playgroud)

Asp*_*eri 4

修复方法是将弹出控制器添加到控制器层次结构中

let popover = FlagPopUpController(delegateController: self)
popover.presentationController?.delegate = self
popover.displayFlagPopUp()
self.addChild(popover)              // << here !!
popover.didMove(toParent: self)     // << here !!
Run Code Online (Sandbox Code Playgroud)

使用 Xcode 14b5 / iOS 16 进行测试