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

329 views Asked by At

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
1

There are 1 answers

0
DonMag On BEST ANSWER

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