Reputation: 373
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.
Upvotes: 0
Views: 571
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:
drag the red view up and to the left a bit:
rotate the device:
Upvotes: 1
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