Search code examples
swiftautolayoutframeanchorpoint

How to make the uiview not move after setting its layer's anchorPoint under Autolayout


I have a requirement that need change the UIView layer's anchorPoint, but the view cannot be moved after changing anchorPoint. I know it is possible when the view is defined by frame(CGRect:...). like this:

let width = SCREEN_WIDTH - 40
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2

This works.

But my view is defined by Autolayout, I try the solution like above code, but it doesn't work. Code:

let view1 = UIView()
view1.backgroundColor = .orange
self.view.addSubview(view1)
view1.snp.makeConstraints { (maker) in
     maker.top.equalToSuperview().offset(50)
     maker.leading.equalToSuperview().offset(20)
     maker.trailing.equalToSuperview().offset(-20)
     maker.height.equalTo(200)
}
let oldFrame1 = view1.frame
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view1.frame = oldFrame1

The result is: the orange view1 be moved, it should be like the blue view2 after changing anchorPoint.

enter image description here

So can anyone give me some suggestions?

------------------------------Update Answer-----------------------------

Just as @DonMag answer, we can implement this requirement by updating the constraints of view not frame when using Autolayout. Here is the code by SnapKit:

let view1 = UIView()
view1.backgroundColor = .orange
view1.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view1)
view1.snp.makeConstraints { (maker) in
    maker.top.equalToSuperview().offset(100)
    maker.leading.equalToSuperview().offset(20)
    maker.trailing.equalToSuperview().offset(-20)
    maker.height.equalTo(200)
}
        
// important!!!
view1.layoutIfNeeded()

let oldFrame1 = view1.frame
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)

// update constraints by updateConstraints function
// if you use @IBOutlet NSLayoutConstraint from xib,
// you can also just set xxx.constant = yyy to update the constraints.
view1.snp.updateConstraints { (maker) in
    let subOffset = oldFrame1.width * 0.5
    maker.leading.equalToSuperview().offset(20 - subOffset)
    maker.trailing.equalToSuperview().offset(-20 - subOffset)
}

let width = SCREEN_WIDTH - 40
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2

Another solution is to change the autolayout view to frame by setting translatesAutoresizingMaskIntoConstraints = true, like this:

let width = SCREEN_WIDTH - 40
let view1 = UIView()
view1.backgroundColor = .orange

self.view.addSubview(view1)
// Autolayout
view1.snp.makeConstraints { (maker) in
    maker.top.equalToSuperview().offset(100)
    maker.leading.equalToSuperview().offset(20)
    maker.trailing.equalToSuperview().offset(-20)
    maker.height.equalTo(200)
}

// change autolayout to frame
view1.translatesAutoresizingMaskIntoConstraints = true
view1.frame = CGRect(x: 20, y: 100, width: width, height: 200)
let oldFrame1 = view1.frame
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view1.frame = oldFrame1

let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2

Solution

  • First, when using auto-layout / constraints, setting the view's .frame directly will not give desired results. As soon as auto-layout updates the UI, the constraints will be re-applied.

    When you change the .anchorPoint you change the geometry of the view. For that reason, you may be better off using .frame instead of auto-layout.

    If you do need / want to use auto-layout, you'll need to update the .constant values of the constraints to account for the geometry changes.

    I don't know how to do that with SnapKit, but here is an example using "standard" constraint syntax.

    • Declare Leading and Trailing constraint variables
    • assign and activate the constraints
    • tell auto-layout to calculate the frame
    • change the anchorPoint
    • update the Leading and Trailing constraint constants to reflect the geometry change

    Note: this is example code only!:

    class ViewController: UIViewController {
        
        // these will have their .constant values changed
        //  to account for layer.anchorPoint change
        var leadingConstraint: NSLayoutConstraint!
        var trailingConstraint: NSLayoutConstraint!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // NOT using auto-layout constraints
            let width = view.frame.width - 40
            let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
            view2.backgroundColor = .blue
            self.view.addSubview(view2)
            let oldFrame2 = view2.frame
            view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
            view2.frame = oldFrame2
            
            // USING auto-layout constraints
            let view1 = UIView()
            view1.backgroundColor = .orange
            view1.translatesAutoresizingMaskIntoConstraints = false
            self.view.addSubview(view1)
    
            // create leading and trailing constraints
            leadingConstraint = view1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0)
            trailingConstraint = view1.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0)
            
            // activate constraints
            NSLayoutConstraint.activate([
                view1.topAnchor.constraint(equalTo: view.topAnchor, constant: 80.0),
                view1.heightAnchor.constraint(equalToConstant: 200.0),
                leadingConstraint,
                trailingConstraint,
            ])
    
            // auto-layout has not run yet, so force it to layout
            //  the view frame
            view1.layoutIfNeeded()
            
            // get the auto-layout generated frame
            let oldFrame1 = view1.frame
            
            // change the anchorPoint
            view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
            
            // we've move the X anchorPoint from 0.5 to 0.0, so
            //  we need to adjust the leading and trailing constants
            //  by 0.5 * the frame width
            leadingConstraint.constant -= oldFrame1.width * 0.5
            trailingConstraint.constant -= oldFrame1.width * 0.5
    
        }
        
    }
    

    Result:

    enter image description here