ckc
ckc

Reputation: 373

Center of subview after device orientation

I have posted a similar question here UIView Position After Device Orientation

I am designing an app with a single ViewController and a subview of the main view. This subview is a map. Initially the center of it is equal to the center of the main view (and equal to center of the device). The position of this subview can change using a gesture, so it does not have any layout constraints. When the orientation of the device changes, the default behaviour is that the center of the subview is moved in a way that its horizontal and vertical distance from the upper left corner of the device, remain unchanged. I am trying to change this so that the distances to remain unchanged would be the horizontal and vertical distance between the subview's center and the main view's center (which, after the orientation, remains equal to the center of the device). I have tried the following approaches:

First approach (It works fine for iphone, but not for ipad, since it has regular trait collections for vertical and horizontal class for both orientations.)

var distanceOfCenters: CGPoint?
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.willTransition(to: newCollection, with: coordinator)
    distanceOfCenters = CGPoint(x: view.subviews[0].center.x-view.center.x, y: view.subviews[0].center.y-view.center.y)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
    super.traitCollectionDidChange(previousTraitCollection)
    if let x = distanceOfCenters?.x, let y = distanceOfCenters?.y
    {
        view.subviews[0].center = CGPoint(x: view.center.x+x, y: view.center.y+y)
    }
}

Second aprooach (It works both for iphone and ipad, but it fails in the case where the device rotates immdediately from left landscape to right landscape, i.e., without first passing from protrait. It also does not operate smoothly)

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.viewWillTransition(to: size, with: coordinator)
    view.subviews[0].center.x += view.center.y - view.center.x
    view.subviews[0].center.y += view.center.x - view.center.y
}

Any ideas how to implement this? I find it quite strange that it is so difficult to add such a simple functionality in the app.

Here are some screenshots. The black dot is the center of the main view. When the device is rotated to landscape, it should be like in screenshot 3, where the distance between the subviews's center and main view's center remains constant after the rotation to landscape. The default is like screenshot 2 where the distance between the subviews's center and the upper left corner of the main view remains constant after the rotation to landscape.

enter image description here

enter image description here

enter image description here

Upvotes: 0

Views: 571

Answers (2)

DonMag
DonMag

Reputation: 77423

Here's a basic example of using constraints and letting auto-layout handle the positioning when the device is rotated.

It creates a small, round blue "dot" in the center of the view, and a 100x100 red square view to drag around - it's translucent, so we can see the dot through it.

On viewDidAppear() we set the top-left corner of the red "dragView" at the center if the view (also the center if the blue "dotView":

class DragTestViewController: UIViewController {
    
    let dotView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .systemBlue
        return v
    }()
    
    let dragView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = UIColor.systemRed.withAlphaComponent(0.75)
        return v
    }()
    
    // drag view's center constraints - created in viewDidLoad()
    var centerX: NSLayoutConstraint!
    var centerY: NSLayoutConstraint!

    // these are used by the pan gesture handler
    var currentCenterX: CGFloat = 0
    var currentCenterY: CGFloat = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(dotView)
        view.addSubview(dragView)
        
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        // create centerX and centerY constraints for dragView
        centerX = dragView.centerXAnchor.constraint(equalTo: g.centerXAnchor)
        centerY = dragView.centerYAnchor.constraint(equalTo: g.centerYAnchor)
        
        NSLayoutConstraint.activate([
            
            // put 8x8 dotView in center
            dotView.widthAnchor.constraint(equalToConstant: 8.0),
            dotView.heightAnchor.constraint(equalTo: dotView.widthAnchor),
            dotView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            dotView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            // dragView width & height 100x100
            dragView.widthAnchor.constraint(equalToConstant: 100.0),
            dragView.heightAnchor.constraint(equalTo: dragView.widthAnchor),
            
            // activate dragView's center anchors
            centerX,
            centerY
        ])
        
        let pan = UIPanGestureRecognizer(target: self, action: #selector(self.panGestureHandler(_:)))
        dragView.addGestureRecognizer(pan)

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // make dotView round
        dotView.layer.cornerRadius = dotView.bounds.width * 0.5
        
        // start with dragView's top-left corner at center of view
        centerX.constant = dragView.bounds.midX
        centerY.constant = dragView.bounds.midY
    }

    @objc func panGestureHandler(_ recognizer: UIPanGestureRecognizer){
        
        switch recognizer.state {
        case .began:
            // save the current center x and y constant value
            currentCenterX = centerX.constant
            currentCenterY = centerY.constant
            break
            
        case .changed:
            // update dragView's center x & y, based on pan motion
            let translation = recognizer.translation(in: self.view)
            centerX.constant = currentCenterX + translation.x
            centerY.constant = currentCenterY + translation.y
            break
            
        case .ended, .cancelled:
            // if you want to do something when pan ends
            break
            
        default:
            break
        }
        
    }

}

On launch:

enter image description here

drag the red view up and to the left a bit:

enter image description here

rotate the device:

enter image description here

Upvotes: 1

iUrii
iUrii

Reputation: 13768

For positioning your subviews try to use viewWillLayoutSubviews and viewDidLayoutSubviews e.g.:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
        
    view.subviews[0].center.x += view.center.y - view.center.x
    view.subviews[0].center.y += view.center.x - view.center.y
}

Upvotes: 0

Related Questions