Search code examples
iosswiftuitextfielduitextviewsubview

How to center multiple UI elements within a parent UIView


I'm trying to create these 6 UITextField centered within a UIVIew that I have centered and is 0.85 the width of self.view. I was able to get it working on one iPhone size however it was hardcoded and doesn't transform well on other iPhone sizes.

So now I'm trying to figure out the best way to properly center these 6 elements.

Here's what I currently have:

class FormView: UIViewController {
    //INSTANTIATING VARIABLES
    ...


    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        setupCodeFieldView()
    }
    
    private func setupCodeFieldView(){
        codeFieldView.backgroundColor = .green
        codeFieldView.translatesAutoresizingMaskIntoConstraints = false
        parentSubview.addSubview(codeFieldView)
        codeFieldView.heightAnchor.constraint(equalToConstant: 60).isActive = true
        codeFieldView.topAnchor.constraint(equalTo: parentSubview.bottomAnchor, constant: 5).isActive = true
        //SETTING WIDTH SIZE AND CENTERING PARENT VIEW
        codeFieldView.widthAnchor.constraint(equalTo:view.widthAnchor, multiplier: 0.85).isActive = true
        codeFieldView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        
        setupCodeFields()
    }

    fileprivate func setupCodeFields() {
        var textFieldArr: [UITextField] = []
        for index in 0...5{
            let field: UITextField = UITextField()
            field.returnKeyType = .next
            field.setUnderline()
            field.borderStyle = .none
            field.keyboardType = .numberPad
            field.backgroundColor = UIColor.white
            field.tag = index
            field.textAlignment = .center
            let valueWidth = codeFieldView.bounds.size.width/6
            field.placeholder = String(describing: valueWidth)
            field.accessibilityIdentifier = "field" + String(index)
            codeFieldView.addSubview(field)
            field.layer.cornerRadius = 5.0
            field.layer.borderWidth = 1.0
            field.layer.borderColor = UIColor(red: 0.45, green: 0.46, blue: 0.50, alpha: 1.00).cgColor
            field.translatesAutoresizingMaskIntoConstraints = false
            
            //HERE IS HOW SET THE WIDTH OF THE BUTTON
            field.widthAnchor.constraint(equalToConstant: floor(valueWidth)-5).isActive = true
            field.heightAnchor.constraint(equalToConstant: 60).isActive = true
            field.topAnchor.constraint(equalTo: codeFieldView.topAnchor).isActive = true
            if index == 0 {
                field.leftAnchor.constraint(equalTo:codeFieldView.leftAnchor).isActive = true
                
            } else {
                field.leftAnchor.constraint(equalTo: textFieldArr[index-1].rightAnchor, constant: 5).isActive = true
            }
            textFieldArr.append(field)
        }
    }
}

Here's what I currently have. You can see that the 6 elements' parent view is centered and that I'm struggling to have the 6 children UITextFields perfectly spaced across the highlighted green parent view. enter image description here

Mockup of how I'd like my UI to look: Mockup on how I'd like my UI to look


Solution

  • You can do this easily with a UIStackView

    Here's a quick example (based on your code):

    class FormView: UIViewController {
        //INSTANTIATING VARIABLES
        //...
        
        let codeFieldView = UIView()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .gray
            
            setupCodeFieldView()
    
        }
        
        private func setupCodeFieldView() {
            codeFieldView.backgroundColor = .green
            codeFieldView.translatesAutoresizingMaskIntoConstraints = false
            
            // not clear what you're doing with "parentSubview"
            //  so let's just add it to the root view
            
            view.addSubview(codeFieldView)
            
            // always respect safe-area
            let g = view.safeAreaLayoutGuide
            
            NSLayoutConstraint.activate([
                
                // height of 60
                codeFieldView.heightAnchor.constraint(equalToConstant: 60),
                // 85% of the width
                codeFieldView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.85),
                // centered vertically and horizontally
                codeFieldView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
                codeFieldView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
    
            ])
            
            setupCodeFields()
        }
        
        fileprivate func setupCodeFields() {
            
            // let's add a stack view to codeFieldView
            let stackView = UIStackView()
            stackView.spacing = 5
            stackView.distribution = .fillEqually
            
            stackView.translatesAutoresizingMaskIntoConstraints = false
            codeFieldView.addSubview(stackView)
            
            NSLayoutConstraint.activate([
                // constrain stack view to all 4 sides of code field view
                stackView.topAnchor.constraint(equalTo: codeFieldView.topAnchor),
                stackView.leadingAnchor.constraint(equalTo: codeFieldView.leadingAnchor),
                stackView.trailingAnchor.constraint(equalTo: codeFieldView.trailingAnchor),
                stackView.bottomAnchor.constraint(equalTo: codeFieldView.bottomAnchor),
            ])
            
            // now we add the text fields
            for index in 0...5 {
                let field: UITextField = UITextField()
                field.returnKeyType = .next
                //field.setUnderline()
                field.borderStyle = .none
                field.keyboardType = .numberPad
                field.backgroundColor = UIColor.white
                field.tag = index
                field.textAlignment = .center
                field.accessibilityIdentifier = "field" + String(index)
                field.layer.cornerRadius = 5.0
                field.layer.borderWidth = 1.0
                field.layer.borderColor = UIColor(red: 0.45, green: 0.46, blue: 0.50, alpha: 1.00).cgColor
                
                // add it to the stack view
                stackView.addArrangedSubview(field)
            }
            
        }
    
    }
    

    The result:

    enter image description here

    Your question didn't indicate how you want the UI to look on a wider device, so here's how that looks when the phone is rotated:

    enter image description here