skip to Main Content

I’m trying center subview. But it doesn’t seem expected. How can I fix it?

here is my codes

private lazy var uploadButton: UIButton = {
        let button = UIButton()
        let innerView = UIView(frame: CGRect(x: 0, y: 0, width: 136, height: 63))
        innerView.backgroundColor = .cyan
        innerView.center = CGPoint(x: button.frame.size.width/2, y: button.frame.size.height/2)
        button.addSubview(innerView)
        button.layer.cornerRadius = 30
        button.backgroundColor = .white
        button.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
        button.layer.shadowOpacity = 1
        button.layer.shadowOffset = CGSize.zero
        button.layer.shadowRadius = 6
        return button
    }()
view.addSubview(uploadButton)
uploadButton.translatesAutoresizingMaskIntoConstraints = false
// ...
uploadButton.bottomAnchor.constraint(equalTo: nextButton.topAnchor, constant: -69),
            uploadButton.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 38),
            uploadButton.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -38),
            uploadButton.heightAnchor.constraint(equalToConstant: 171)
// all active

enter image description here

2

Answers


  1. innerView.center = CGPoint(x: button.frame.size.width/2, y: button.frame.size.height/2)
    button.addSubview(innerView)

    I think your code is failing because innerView doesn’t have a superview at the time you set its center property. From the docs:

    The center point is specified in points in the coordinate system of its superview. Setting this property updates the origin of the rectangle in the frame property appropriately.

    What you want is to set innerView‘s position in button. Accordingly, you should add innerView as a subview of button first, and then set its center. So, swapping those two lines should solve your problem.

    Login or Signup to reply.
  2. There are different layout systems available in iOS

    1. Auto Layout
    2. Frame-Based Layout
    3. Stack Views
    4. SwiftUI

    Now, why this inconsistency arises in your snippet?

    Your code snippet above has used both Auto Layout and Frame-based Layout which will barely give you desired layout or offer a consistent way for maintenance.

    Let’s have a look at one case.

    • uploadButton is based on autolayout.
    • innerView is based on frame-based layout.
    • Auto layout will dynamically calculate the view size of your uploadButton, so when you want to set innerView center using button.frame.size early it won’t work as the width and height are zero that time, and will be calculated later upon constraint activation.
    • If uploadButton had frame-based layout only then button.frame.size related calculations would have made sense.

    TLDR

    Stick with only ONE layout system to maintain consistency and intuitiveness as mixing multiple systems can lead to confusion and maintenance issues.

    Code

    Here, is a working auto layout based view.

    private lazy var innerView: UIView = {
        let innerView = UIView()
        innerView.translatesAutoresizingMaskIntoConstraints = false
        innerView.backgroundColor = .cyan
        return innerView
    }()
    
    private lazy var uploadButton: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.backgroundColor = .white
        return button
    }()
    
    ....
    ....
    
    // then in viewDidLoad() where you structure view elements
    view.addSubview(uploadButton)
    uploadButton.addSubview(innerView)
    
    // Try replacing this constraint activation part with any utility extension 
    // or storyboard or available library with less code 
    
    let uploadButtonConstraints = [
        uploadButton.leftAnchor.constraint(equalTo: view.leftAnchor),
        uploadButton.rightAnchor.constraint(equalTo: view.rightAnchor),
        uploadButton.topAnchor.constraint(equalTo: view.topAnchor),
        uploadButton.bottomAnchor.constraint(equalTo: view.bottomAnchor)
    ]
    NSLayoutConstraint.activate(uploadButtonConstraints)
    
    let innerViewConstraints = [
        innerView.heightAnchor.constraint(equalToConstant: 63), // hardcoding is bad
        innerView.widthAnchor.constraint(equalToConstant: 136),
        innerView.centerXAnchor.constraint(equalTo: uploadButton.centerXAnchor),
        innerView.centerYAnchor.constraint(equalTo: uploadButton.centerYAnchor)
    ]
    NSLayoutConstraint.activate(innerViewConstraints)
    ....
    

    Result

    enter image description here

    Offtopic?

    When designing an API for developers in a language or platform, it is ideal to provide a single approach to accomplish a task. This simplifies the API and minimizes confusion, for example, as demonstrated by the guiding principles of Go language design.

    However, as a language or platform evolves, it goes through various phases of development, such as the transition of iOS app layout from Frame-based to Auto Layout to SwiftUI. To maintain backward compatibility previous APIs are kept available as well, which results in developers being exposed to multiple approaches to achieve the same goal or view, leading to confusion when those approaches get mixed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search