I need this grid layout, view structure with Storyboard. Is it an easier way to set up, or I need to calculate size / 4, and multiply it by the index, and calculate the center X, Y coordinates, and adjust NSLayoutConstraint at each rotation?
You can use UIStackView with default vertical and change it's axis to horizontal in landscape size class , with distribution Fill-Equally
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation == .portrait
{
self.stackV.axis = .vertical
}
else
{
self.stackV.axis = .horizontal
}
}
The above answer is perfect but we can also handle it by using storyboard.To explain I get 3 UIView into a stack view with below property.
Now when I go to landscape from portrait mode the height of the screen is converting to compact from regular.So we can change stack view axis from vertical to horizontal according to height change of the screen.below gif explain you visually.
for further information you can visit this link.
Related
Currently, on iPad, my UINavigationBar large title looks strange because it does not indent to where my UITableView cells are. This is because my table view and cells follow readable width. Is there a way of indenting this title or is the only piece of advice to align the cells with the title (make them not follow readable width). I would really appreciate anyone who took the time to respond.
When you first load your view controller, you should retrieve the minX coordinate of the current readableContentGuide and set that value to the left layoutMargin value. As so:
let leftMargin = self.view.readableContentGuide.layoutFrame.minX
self.navigationController?.navigationBar.layoutMargins.left = leftMargin
Then, you'll also want to make sure your layout adjusts dynamically so that if the user rotates their screen (or on an iPad, is using a variable window size) the title will remain aligned:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
let leftMargin = view.readableContentGuide.layoutFrame.minX
navigationController?.navigationBar.layoutMargins.left = leftMargin
}, completion: nil)
}
The answer of #swillsea did not work correctly for me: on rotation of the device the left margin of the large title seems to have a wrong initial position during the rotation animation. One can see this in the simulator with the options for slow animations enabled.
The reason for this is that the screen is laid out in the new dimension using the old left margin. During the rotation animation the margin is animated into the correct final size.
Instead of updating the left margin when the size changes I simply update as part of the normal layout chain:
override func viewWillLayoutSubviews() {
let leftMargin = self.view.readableContentGuide.layoutFrame.minX
self.navigationController?.navigationBar.layoutMargins.left = leftMargin
}
The effect for me is now, that the left margin of the title is now equal to that of my readable content in all phases of the rotation animation.
I want my view to have the following properties (the numbers are arbitrarily chosen):
width is equal to height divided by 1.2
stays at the bottom right of the screen
height is 1/7 of the screen's height when in portrait
width and height does not change when the orientation changes
The first three requirements can be easily translated into UILayoutConstraints. I have done them with SnapKit just because it reads more clearly. You should see what I mean even if you have never used SnapKit before.
let myView = UIView(frame: .zero)
myView.backgroundColor = .green
view.addSubview(myView)
myView.snp.makeConstraints { (make) in
make.right.equalToSuperview().offset(-8)
make.bottom.equalToSuperview().offset(-8)
make.width.equalTo(myView.snp.height).dividedBy(1.2)
make.height.equalTo(view.snp.height).dividedBy(7) // *
}
The problem is the last bullet point. When I rotate the device from portrait to landscape, what was originally the width before the rotation, becomes the height after the rotation. This causes my view to become smaller as a result.
Basically, I want to replace the constraint marked with * with something like this:
make.height.equalTo(max(view.snp.height, view.snp.width)).dividedBy(7)
but I don't think max(a, b) is a thing in SnapKit or the UILayoutConstraint API.
Surely there is some other way of expressing "equal to whichever length is longer", right?
P.S. I didn't tag this with snapkit because I would also accept an answer that uses the UILayoutConstraint API.
Looks like you have 2 options:
Hardcode the height value.
Try to use nativeBounds:
This rectangle is based on the device in a portrait-up orientation. This value does not change as the device rotates.
In this case the height is always be for portrait mode.
myView.snp.makeConstraints { make in
make.right.bottom.equalToSuperview().offset(-8)
let screenHeight = UIScreen.main.nativeBounds.height / UIScreen.main.nativeScale
let height = screenHeight / 7
make.width.equalTo(height).dividedBy(1.2)
make.height.equalTo(height)
}
This question already has answers here:
How to make a square view resize with its superview using auto layout
(4 answers)
Closed 7 years ago.
This seems like it should be easier that it currently is being.
I am trying to center a UIView with an aspect ratio of 1:1 (A square) in any iOS device it is drawn in, regardless of orientation.
For detail:
My view has a draw that updates on a timeInterval. I was using a full screen view and computing my square on each draw. On orientation change the whole view went to hell. I assumed that if the view was square, I could trust the orientation change animation.
My constraints have been failing repeatedly, which is why this seems like it should be easier:
View (Square)
Constraints
aspect 1:1
Constraints
View.centerX = centerX
View.centerY = centerY
View.leading ≥ leadingMargin + 5 # 800
View.top ≥ Top Layout Guide.bottom + 5 # 800
trailingMargin ≥ View.trailing + 5 # 800
Bottom Layout Guide.top ≥ View.bottom + 5 # 800
I have the Content Hugging Prioity at 250
I have the content Compression Resistance at 750
This leaves the constraint errors:
- Missing Constraint: Need constrains for: X position or width
- Missing Constraint: Need constrains for: Y position or height
My confusion is that I can't lock into one dimension because on rotation I need to lock into the other.
As mentioned... this seems like it should be easier.
Center a Square UIView with a border of 5 at the thinner dimension.
(5 to the sides in portrait, 5 to the top and bottom in landscape)
Suggestions warmly appreciated, Explanations would be beyond helpful.
Here is an approach that I just put together. I couldn't do it entirely in IB, but at least the run-time code is limited to activating/deactivating constraints rather than having to add/remove or compute/change any sizes.
In my storyboard I have an inner UIView with the following constraints
Center X
Center Y
Leading space to superview = 5, priority = 999
Trailing space to superview = 5, priority = 999
Top space to superview=5, priority=1000
Bottom space to superview=5, priority=1000
I created IBOutlets for the last four constraints and this is my view controller -
class ViewController: UIViewController {
#IBOutlet var leadingConstraint: NSLayoutConstraint!
#IBOutlet var trailingConstraint: NSLayoutConstraint!
#IBOutlet var topConstraint: NSLayoutConstraint!
#IBOutlet var bottomConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.setConstraintsForSize(self.view.frame.size)
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
self.setConstraintsForSize(size)
}
func setConstraintsForSize(size:CGSize) {
if (size.width > size.height) {
self.leadingConstraint.active=false;
self.trailingConstraint.active=false;
self.topConstraint.active=true;
self.bottomConstraint.active=true;
} else {
self.leadingConstraint.active=true;
self.trailingConstraint.active=true;
self.topConstraint.active=false;
self.bottomConstraint.active=false;
}
}
}
This works on iPhone and iPad, including iPad in windowed/splitscreen mode
The square needs to specify its size.
Square centerX = Parent centerX
Square centerY = Parent centerY
At this point, the view is centered, but there's nothing to let autolayout determine the size of the view.
Square width = 200
Square aspect = 1
If you want the square to be a portion of the parent's width, that's straightforward as well.
Square width = Parent width * 0.5 //or any other multiplier
You'll need to adjust the width constraint depending on how you want the square to look in landscape.
To have "the largest square possible", you can programmatically set the width constraint to min(parentWidth, parentHeight) and adjust on orientation.
Alternatively, leading, trailing, centerX, centerY, and aspect should get you similar results. Any set of constraints needs to be able to logically determine position and size without ambiguity.
Update:
In IB:
Square centerX = Parent centerX
Square centerY = Parent centerY
Square aspect = 1
In code:
if parent width > parent height
Square width = parent height - 2 * margin
else
Square width = parent width - 2 * margin
Adjust this on rotation
Here is my problem: rotate an iPhone 6 Plus or an iPad to e.g. LandscapeLeft.
Lets say your app has the following code (only for example)
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
// mine does use All, but with this case you'll understand my question
}
How do I detect if the device should use vertical or horizontal layout when
func willTransitionToTraitCollection(newCollection: UITraitCollection, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
is called?
print(UIScreen.mainScreen().bounds.width < UIScreen.mainScreen().bounds.height)
// will return false, because we started in landscape but the layout will be forced to be vertical here
print(UIApplication.sharedApplication().statusBarOrientation == .LandscapeLeft)
// will return true, because we started in landscape but the layout will be forced to be vertical here
The main problem appears on the iPad, because both size classes are regular.
I'm confused and have no idea how to solve this issue.
Don't think of your layouts as vertical and horizontal. Just let the app rotate, and respond to the size class or the size change.
On the iPad, try to avoid having two layouts. Just use autolayout to rejigger the proportions automatically. But if you must respond to rotation on the iPad, then respond to viewWillTransitionToSize (because the trait collection won't change).
I have several UIViews laid out along the bottom of a containing UIView. I want these views to always be equal width, and always stretch to collectively fill the width of the containing view (like the emoji keyboard buttons at the bottom). The way I'm approaching this is to set equal widths to one of the views, then just update the width constraint of that view to be superviewWidth / numberOfViews which will cause all of the other views to update to that same value.
I am wondering where the code to change the constraint constant needs to go. It needs to be set before the keyboard appears on screen for the first time and update when rotating the device.
My first attempt at a solution was to place it in updateViewConstraints and calculate the width via containerView.frame.size.width. But this method is called twice upon load, the first time it calculates the values correctly, but the second time for some reason the containerView's width is 0.0. Another issue is that when rotating, the containerView's width is not the value that it will be after rotation, it's the current value before rotation. But I don't want to wait until after the rotation completes to update the constraint, because the buttons will be the original size then change which will be jarring to the user.
My question is: where is the most appropriate place to put this code? Is there a better way to calculate what the width will be? I can guarantee it will always be the exact same width as the screen width. And I am using Size Classes in Xcode 6, so willRotateToInterfaceOrientation and similar methods are deprecated.
On all classes that implement the UITraitEnvironment protocol the method traitCollectionDidChange will be called when the trait collection changes, like on rotation. This is the appropiate place to manually update the constraints when using the new Size Classes. You can also animate the transition with the method willTransitionToTraitCollection
Basic example:
class ViewController: UIViewController {
var constraints = [NSLayoutConstraint]()
func updateConstraintsWithTraitCollection(traitCollection: UITraitCollection) {
// Remove old constraints
view.removeConstraints(constraints)
// Create new constraints
}
override func willTransitionToTraitCollection(newCollection: UITraitCollection!,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!) {
super.willTransitionToTraitCollection(newCollection, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext!) in
self.updateConstraintsWithTraitCollection(newCollection)
self.view.setNeedsLayout()
}, completion: nil)
}
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection!) {
updateConstraintsWithTraitCollection(traitCollection)
}
}
Besides that I want to recommend Cartography, which is a nice library that helps to make auto layout more readable and enjoyable. https://github.com/robb/Cartography
There is no reason to update the width manually:
Place all the views with equal width in your view with no spacing in between each other
Add an equal width constraint to all of them
Add constraints with 0 width for spacing between sides and each other
Lower the priority of one or more of the equal width constraints just in case the width cannot be divided equally.
Then auto layout will handle everything for you.