简体   繁体   English

使用带有自动布局的自定义 UIView class 返回不正确的边界

[英]Using custom UIView class with Auto Layout returns incorrect bounds

I'm presenting a simple view controller.我正在展示一个简单的视图 controller。 To follow MVC, I moved my programmatic view code into a separate UIView subclass.为了遵循 MVC,我将我的编程视图代码移动到一个单独的 UIView 子类中。 I followed the advice here of how to set up the custom view.我遵循了此处有关如何设置自定义视图的建议。

This works ok in iPhone.这在 iPhone 中可以正常工作。 But, on iPad, the view controller is automatically presented with the new post-iOS 13 default modal style .pageSheet , which causes one of my buttons to be too wide.但是,在 iPad 上,视图 controller 会自动显示新的 iOS 13 后默认模式样式.pageSheet ,这会导致我的一个按钮太宽。 I looked into the view debugger and it's because the width constraint is set to self.bounds.width * 0.7 , and self.bounds returns the full width of the iPad (1024 points) at the time the constraint is set, not the actual final view (which is only 704 points wide).我查看了视图调试器,这是因为宽度约束设置为self.bounds.width * 0.7 ,并且self.bounds在设置约束时返回 iPad 的全宽(1024 点),而不是实际的最终宽度视图(只有 704 点宽)。

iPhone simulator screenshot iPhone模拟器截图

iPad 12.9" simulator screenshot iPad 12.9" 模拟器截图

Three questions:三个问题:

  1. In general, am I setting up the custom view + view controller correctly?一般来说,我是否正确设置了自定义视图 + 视图 controller? Or is there a best practice I'm not following?或者有没有我没有遵循的最佳实践?

  2. How do I force an update to the constraints so that the view recognizes it's being presented in a .pageSheet modal mode on iPad, and automatically update the width of self.bounds ?如何强制更新约束,以便视图识别它在 iPad 上以.pageSheet模态模式呈现,并自动更新self.bounds的宽度? I tried self.view.setNeedsLayout() and self.view.layoutIfNeeded() but it didn't do anything.我尝试了self.view.setNeedsLayout() and self.view.layoutIfNeeded()但它没有做任何事情。

  3. Why is it that the Snooze button is laid out correctly on iPad, but not the Turn Off Alarm button... the Snooze button relies on constraints pinned to self.leadingAnchor and self.trailingAnchor .为什么 Snooze 按钮在 iPad 上的布局正确,而不是 Turn Off Alarm 按钮……Snooze 按钮依赖于固定到self.leadingAnchorself.trailingAnchor的约束。 Why do those constraints correctly recognize the modal view they're being presented in, but self.bounds.width doesn't?为什么这些约束能够正确识别它们正在呈现的模态视图,但self.bounds.width不能?

Code :代码

Xcode project posted here Xcode 项目发布在这里

View Controller using a custom view:使用自定义视图查看 Controller:

class CustomViewController: UIViewController {
    
    var customView: CustomView {
        return self.view as! CustomView
    }
    
    override func loadView() {
        let customView = CustomView(frame: UIScreen.main.bounds)
        self.view = customView  // Set view to our custom view
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        print("View's upon viewDidLoad is: \(self.view.bounds)") // <-- This gives incorrect bounds

        // Add actions for buttons
        customView.snoozeButton.addTarget(self, action: #selector(snoozeButtonPressed), for: .touchUpInside)
        customView.dismissButton.addTarget(self, action: #selector(dismissButtonPressed), for: .touchUpInside)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        print("View's bounds upon viewDidAppear is: \(self.view.bounds)") // <-- This gives correct bounds
        
        // This doesn't work
//      //self.view.setNeedsLayout()
//      // self.view.layoutIfNeeded()

    }

    
    @objc func snoozeButtonPressed() {
        // Do something here
    }
    
    @objc func dismissButtonPressed() {
        self.dismiss(animated: true, completion: nil)
    }

}

Custom view code:自定义视图代码:

class CustomView: UIView {
    
    public var snoozeButton: UIButton = {
        let button = UIButton(type: .system)
        button.isUserInteractionEnabled = true
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Snooze", for: .normal)
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
        button.setTitleColor(UIColor.white, for: .normal)
        button.layer.borderWidth = 1
        button.layer.borderColor = UIColor.white.cgColor
        button.layer.cornerRadius = 14
        return button
    }()
    
    public var dismissButton: UIButton = {
        let button = UIButton(type: .system)
        button.isUserInteractionEnabled = true
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Turn off alarm", for: .normal)
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
        button.setTitleColor(UIColor.white, for: .normal)
        button.backgroundColor = UIColor.clear
        button.layer.cornerRadius = 14
        button.layer.borderWidth = 2
        button.layer.borderColor = UIColor.white.cgColor
        return button
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupUI()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupUI() {
        self.backgroundColor = .systemPurple
        
        // Add subviews
        self.addSubview(snoozeButton)
        self.addSubview(dismissButton)
    }
    
    func setupConstraints() {
        
        print("Self.bounds when setting up custom view constraints is: \(self.bounds)") // <-- This gives incorrect bounds 
        
        NSLayoutConstraint.activate([
        
            dismissButton.heightAnchor.constraint(equalToConstant: 60),
            dismissButton.widthAnchor.constraint(equalToConstant: self.bounds.width * 0.7),  // <-- This is the constraint that's wrong 
            dismissButton.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            dismissButton.centerYAnchor.constraint(equalTo: self.centerYAnchor),
        
            snoozeButton.heightAnchor.constraint(equalToConstant: 60),
            snoozeButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
            snoozeButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20),
            snoozeButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -40)
    
        ])
            
    }
}

.. and finally, the underlying view controller doing the presenting: .. 最后,底层视图 controller 进行演示:

class BlankViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = .white
    }
    
    override func viewDidAppear(_ animated: Bool) {
        let customVC = CustomViewController()
        self.present(customVC, animated: true, completion: nil)
    }
}

Thanks.谢谢。

There's no need to reference bounds -- just use a relative width constraint.无需引用bounds ——只需使用相对宽度约束即可。

Change this line:更改此行:

dismissButton.widthAnchor.constraint(equalToConstant: self.bounds.width * 0.7),  // <-- This gives incorrect bounds

to this:对此:

dismissButton.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7),

Now, the button will be 70% of the view's width.现在,按钮将是视图宽度的70%。 As an added benefit, it will adjust itself if/when the view width changes, instead of being stuck at a calculated Constant value.作为一个额外的好处,如果/当视图宽度发生变化时,它将自行调整,而不是停留在计算出的常量值上。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM