I am in the process of learning auto layout and I use snapkit. I written some code but working differently than expected. I write code leftMargin but it is working as if it's a rightMargin. You can see on photo. what is wrong on my code?
My Code
let container = View()
container.backgroundColor=UIColor.greenColor()
let v1 = View()
v1.backgroundColor=UIColor.blackColor()
self.view.addSubview(container);
container.addSubview(v1)
let padding2 : UIEdgeInsets = UIEdgeInsetsMake(20,20,20,20)
container.snp_makeConstraints { (make) -> Void in
make.top.equalTo(self.view).offset(padding2.top)
make.bottom.equalTo(self.view).offset(-padding2.bottom)
// make.left.equalTo(self.view).inset(padding2.left)
make.left.equalTo(self.view).offset(padding2.left)
make.right.equalTo(self.view).offset(-padding2.right)
//make.width.equalTo(self.view.bounds.width-90)
/*
make.top.equalTo(self.view).offset(20)
make.left.equalTo(self.view).offset(20)
make.bottom.equalTo(self.view).offset(-20)
make.right.equalTo(self.view).offset(-20)
*/
}
let padding : UIEdgeInsets = UIEdgeInsetsMake(50, 50, 15, 10)
v1.snp_makeConstraints { (make) -> Void in
make.topMargin.equalTo(container).offset(padding.top);
make.leftMargin.equalTo(container).offset(padding.left);
make.width.equalTo(100);
make.height.equalTo(100);
}
replace
make.topMargin.equalTo(container).offset(padding.top);
make.leftMargin.equalTo(container).offset(padding.left);
with
make.top.equalTo(container).offset(padding.top);
make.left.equalTo(container).offset(padding.left);
The difference of left and leftMargin? Check this: https://stackoverflow.com/a/28692783/3331750
I've never used snapkit but in regular auto layout you can achieve what you want in the following way.
let container = UIView()
container.backgroundColor=UIColor.greenColor()
let v1 = UIView()
v1.backgroundColor=UIColor.blackColor()
self.view.addSubview(container)
container.addSubview(v1)
// enable the views to be resized by auto layout
container.translatesAutoresizingMaskIntoConstraints = false
v1.translatesAutoresizingMaskIntoConstraints = false
// pin the container to all edges of the main view
container.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor).active = true
container.bottomAnchor.constraintEqualToAnchor(bottomLayoutGuide.topAnchor).active = true
container.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor).active = true
container.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor).active = true
// anchor the v1 subview to the
v1.topAnchor.constraintEqualToAnchor(container.topAnchor, constant: 20).active = true
v1.leadingAnchor.constraintEqualToAnchor(container.leadingAnchor, constant: 20).active = true
// set a fixed height and width
v1.heightAnchor.constraintEqualToConstant(100).active = true
v1.widthAnchor.constraintEqualToConstant(100).active = true
and for proportional height and width replace the final two lines of code with:
v1.widthAnchor.constraintEqualToAnchor(view.widthAnchor, multiplier: 0.25).active = true
v1.heightAnchor.constraintEqualToAnchor(view.widthAnchor, multiplier: 0.25).active = true
Related
I am using snap kit to set out constraints. The first image is what I'm trying to achieve with the code below. How can I set the constraints off the circle's width and height to be dynamic on any iPhone screen ?
profileImage = UIImageView()
profileImage.layer.borderWidth = 2
profileImage.layer.borderColor = UIColor.lightBlue.cgColor
profileImage.layer.cornerRadius = 130
profileImage.clipsToBounds = true
profileImage.layer.masksToBounds = true
let tapGesture = UITapGestureRecognizer(target: self, action:#selector((tappedImage)))
profileImage.addGestureRecognizer(tapGesture)
profileImage.isUserInteractionEnabled = true
profileImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileImage)
profileImage.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(topMargin*2)
make.width.height.equalTo(view.snp.width).multipliedBy(0.71)
}
enter image description here
enter image description here
Couple points...
First, 71% of the view width will probably be too big. Start around 50% and adjust to your liking.
You are using .cornerRadius = 130 but your imageView may not be that size (certainly not on different devices), so you want to set the corner radius to one-half the width of the image view (or height, doesn't matter since it will be a square 1:1 ratio).
You could wait until viewDidLayoutSubviews() to find out the run-time size of the image view, but if your image view ends up as a subview of another view, it won't be set at that point either.
Much easier to create a very simple UIImageView subclass:
class ProfileImageView: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
layer.borderWidth = 2
layer.borderColor = UIColor.systemBlue.cgColor
layer.cornerRadius = bounds.width * 0.5
clipsToBounds = true
layer.masksToBounds = true
}
}
Then your view controller looks like this:
class ViewController: UIViewController {
var profileImage: ProfileImageView!
// your top margin value
let topMargin: CGFloat = 20
override func viewDidLoad() {
super.viewDidLoad()
profileImage = ProfileImageView()
if let img = UIImage(named: "sampleProfilePic") {
profileImage.image = img
}
profileImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileImage)
// respect safe-area
let g = view.safeAreaLayoutGuide
// this should give the same layout as your "snap" constraints
NSLayoutConstraint.activate([
// center horizontally
profileImage.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// your topMargin value * 2 from safe-area top
profileImage.topAnchor.constraint(equalTo: g.topAnchor, constant: topMargin * 2.0),
// width
// 71% of width of safe-area is probably too wide
// try starting at 50%
profileImage.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.50),
// 1:1 ratio (square)
profileImage.heightAnchor.constraint(equalTo: profileImage.widthAnchor),
])
// profileImage.snp.makeConstraints { (make) in
// make.centerX.equalToSuperview()
// make.top.equalTo(titleLabel.snp.bottom).offset(topMargin*2)
// make.width.height.equalTo(view.snp.width).multipliedBy(0.71)
// }
let tapGesture = UITapGestureRecognizer(target: self, action:#selector((tappedImage)))
profileImage.addGestureRecognizer(tapGesture)
profileImage.isUserInteractionEnabled = true
}
}
Whenever the size of your profileImage view changes, the ProfileImageView class' layoutSubviews() will automatically set the corner radius appropriately.
Im new to IOS development and I have two buttons in the UIView and when user select the option portrait or landscape, change the UIView re-size and change the background color as well and i need to add animation for that process.
As as ex:
User select portrait and then user can see red color UIVIew. after click the landscape option, animation should be started and it looks like, red color image come front and change the size (changing height and width) for landscape mode and go to previous position and change color to green. i have added small UIView animation on code and it is helpful to you identify the where should we start the animation and finish it. if someone know how to it properly, please, let me know and appreciate your help. please, refer below code
import UIKit
class ViewController: UIViewController {
let portraitWidth : CGFloat = 400
let portraitHeight : CGFloat = 500
let landscapeWidth : CGFloat = 700
let landscapeHeight : CGFloat = 400
var mainView: UIView!
var mainStackView: UIStackView!
let segment: UISegmentedControl = {
let segementControl = UISegmentedControl()
return segementControl
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.translatesAutoresizingMaskIntoConstraints = false
mainStackView = UIStackView()
mainStackView.axis = .vertical
mainStackView.translatesAutoresizingMaskIntoConstraints = false
mainStackView.alignment = .center
mainStackView.distribution = .equalCentering
self.view.addSubview(mainStackView)
self.segment.insertSegment(withTitle: "Portrait", at: 0, animated: false)
self.segment.insertSegment(withTitle: "Landscape", at: 1, animated: false)
self.segment.selectedSegmentIndex = 0
self.segment.addTarget(self, action: #selector(changeOrientation(_:)), for: .valueChanged)
self.segment.translatesAutoresizingMaskIntoConstraints = false
self.segment.selectedSegmentIndex = 0
mainStackView.addArrangedSubview(self.segment)
let safeAreaLayoutGuide = self.view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
self.mainStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 20),
self.mainStackView.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor, constant: 0),
])
NSLayoutConstraint.activate([
self.segment.heightAnchor.constraint(equalToConstant: 35),
self.segment.widthAnchor.constraint(equalToConstant: 300)
])
mainView = UIView(frame: .zero)
mainView.translatesAutoresizingMaskIntoConstraints = false
mainStackView.addArrangedSubview(mainView)
mainView.backgroundColor = UIColor.red
NSLayoutConstraint.activate([
mainView.heightAnchor.constraint(equalToConstant: portraitHeight),
mainView.widthAnchor.constraint(equalToConstant: portraitWidth),
mainView.topAnchor.constraint(equalTo: segment.bottomAnchor, constant: 30)
])
}
#IBAction func changeOrientation(_ sender: UISegmentedControl) {
self.mainView.constraints.forEach{ (constraint) in
self.mainView.removeConstraint(constraint)
}
UIView.animate(withDuration: 1.0) {
if (sender.selectedSegmentIndex == 0) {
self.mainView.backgroundColor = UIColor.red
NSLayoutConstraint.activate([
self.mainView.heightAnchor.constraint(equalToConstant: self.portraitHeight),
self.mainView.widthAnchor.constraint(equalToConstant: self.portraitWidth),
self.mainView.topAnchor.constraint(equalTo: self.segment.bottomAnchor, constant: 30)
])
} else {
self.mainView.backgroundColor = UIColor.green
NSLayoutConstraint.activate([
self.mainView.heightAnchor.constraint(equalToConstant: self.landscapeHeight),
self.mainView.widthAnchor.constraint(equalToConstant: self.landscapeWidth),
self.mainView.topAnchor.constraint(equalTo: self.segment.bottomAnchor, constant: 30)
])
}
}
}
}
updated logic
#IBAction func changeOrientation(_ sender: UISegmentedControl) {
UIView.animate(withDuration: 4.0) {
if (sender.selectedSegmentIndex == 0) {
self.mainView.backgroundColor = UIColor.red
self.widthConstraint.constant = self.portraitWidth
self.heightConstraint.constant = self.portraitWidth
} else {
self.mainView.backgroundColor = UIColor.green
self.widthConstraint.constant = self.landscapeWidth
self.heightConstraint.constant = self.landscapeHeight
}
self.mainView.layoutIfNeeded()
} }
It's possible, the basic problem is:
• It's not possible to animate an actual change between one constraint and another.
To change size or shape you simply animate the length of a constraint.
While you are learning I would truly urge you to simply animate the constraints.
So, don't try to "change" constraints, simply animate the length of one or the other.
yourConstraint.constant = 99 // it's that easy
I also truly urge you to just lay it out on storyboard.
You must master that first!
Also there is no reason at all for the stack view, get rid of it for now.
Just have an outlet
#IBOutlet var yourConstraint: NSLayoutConstraint!
and animate
UIView.animateWithDuration(4.0) {
self.yourConstraint.constant = 666
self.view.layoutIfNeeded()
}
Be sure to search in Stack Overflow for "iOS animate constraints" as there is a great deal to learn.
Do this on storyboard, it will take 20 seconds tops for such a simple thing. Once you have mastered storyboard, only then move on to code if there is some reason to do so
Forget the stack view
Simply animate the constant of the constraint(s) to change the size, shape or anything you like.
Be sure to google for the many great articles "animating constraints in iOS" both here on SO and other sites!
I have this scene of a neon cassette as you see. In the interface builder I have created 3 UIImageViews, one for the background of the cassette and 2 for the left and right spindles. The idea is that these spindles spin backwards or forwards depending on the state (rewind, ffw or play). I can do this fine in the case that I run this solely on, say, an iPhone 8. However, when I decide I want to run this on an iPhone 11 Pro, the following happens:
No matter what bizarre mixture of constraints I attempt to concoct, I am unable to have the spindles follow their correct position/size across devices. I will have to spend some time finding and reading guides on the beast that is Auto Layout, but until that mountain is climbed, I'd appreciate any help!
EDIT:
Here is a programmatic, so more descriptive, version of what I have so far:
override func viewDidLoad() {
super.viewDidLoad()
// Code to position/size cassette
let cassette = UIImageView(image: UIImage(named: "cassette"))
cassette.translatesAutoresizingMaskIntoConstraints = false
cassette.contentMode = .scaleAspectFit
view.addSubview(cassette)
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: cassette.bottomAnchor, constant: 44).isActive = true
view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: cassette.trailingAnchor, constant: 20).isActive = true
cassette.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
cassette.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 44).isActive = true
// Code to position/size spindles
let spindleLeft = UIImageView(image: UIImage(named: "spindle"))
spindleLeft.translatesAutoresizingMaskIntoConstraints = false
spindleLeft.widthAnchor.constraint(equalToConstant: 74).isActive = true
spindleLeft.heightAnchor.constraint(equalToConstant: 74).isActive = true
cassette.addSubview(spindleLeft)
spindleLeft.centerXAnchor.constraint(equalTo: cassette.centerXAnchor, constant: -130.0).isActive = true
spindleLeft.centerYAnchor.constraint(equalTo: cassette.centerYAnchor, constant: -14.0).isActive = true
}
Here are the 2 assets:
I'm currently stuck trying to figure out if there's some way to use the aspectRatio of the device and what UIImageView Content Mode (Aspect Fit, Aspect Fill) I should use. Perhaps I've dug too deep a whole and there's a far simpler way to deal with this that I'm not seeing...
Mere autolayout is not going to do this. You have to measure where the spindle would be if it were part of the original image, and then mathematically put the spindle there based on the actual size of the image view and where it that puts the image via its content mode.
Let's do that for one spindle (the left spindle). The cassette image you posted is 4824x2230. We discover by careful inspection that if the cassette image were shown at full size, the center of the spindle should be at approximately (1294,1000) and it should be about 500 pixels on a side.
That is the same as saying that we want the image of the spindle to occupy a square whose frame (with respect to the original cassette image) is (1045,750,500,500).
Okay, but that’s just the image. We have to position the spindle relative to the image view that portrays it.
So now I'll show the cassette in an image view that is sized by autolayout to some smaller size. (I can discover what size it is in viewDidLayoutSubviews, which runs after autolayout has done its work.) And let this image view have a content mode of Aspect Fit. The image will be resized, and also its position will probably not be at the image view’s origin.
So the problem you have to solve (in code) is: now where is that spindle frame with respect to the image view once the image has been resized and repositioned? You then just place the spindle image view there.
Here is the code from viewDidLayoutSubviews; cassette is the cassette image view, spindle is the spindle image view:
let cassettebounds = cassette.bounds
let cw = cassettebounds.width
let ch = cassettebounds.height
let w = 4824 as CGFloat
let h = 2230 as CGFloat
var scale = cw/w
var imw = cw
var imh = h*scale
var imx = 0 as CGFloat
var imy = (ch-imh)/2
if imh > ch { // we got it backwards
scale = ch/h
imh = ch
imw = w*scale
imy = 0 as CGFloat
imx = (cw-imw)/2
}
cassette.addSubview(spindle)
spindle.frame.size.width = 500*scale
spindle.frame.size.height = 500*scale
spindle.frame.origin.x = imx + (1045*scale)
spindle.frame.origin.y = imy + (750*scale)
Here's the result on a 6s and an 11 Pro:
Looks darned good if I do say so myself. The right spindle is left as an exercise for the reader.
Thank you to #matt for his superb answer. I definitely was ascribing too much power to auto layout when what was needed was a more rudimentary approach. I've marked his answer correct and will provide my new code that works and supports screens of all sizes:
override func viewDidLoad() {
super.viewDidLoad()
// Code to position/size cassette
cassette = UIImageView(image: UIImage(named: "cassette"))
cassette.translatesAutoresizingMaskIntoConstraints = false
cassette.isUserInteractionEnabled = true
cassette.contentMode = .scaleAspectFit
view.addSubview(cassette)
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: cassette.bottomAnchor, constant: 44).isActive = true
view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: cassette.trailingAnchor, constant: 20).isActive = true
cassette.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
cassette.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 44).isActive = true
}
override func viewDidLayoutSubviews() {
spindleLeft = addSpindle(to: cassette, posX: 505, posY: 350)
spindleRight = addSpindle(to: cassette, posX: 1610, posY: 350)
}
func addSpindle(to cassette: UIImageView, posX: CGFloat, posY: CGFloat) -> UIImageView {
let cassetteBounds = cassette.bounds
let cassetteWidth = cassetteBounds.width
let cassetteHeight = cassetteBounds.height
let cassetteImageWidth = cassette.image!.size.width
let cassetteImageHeight = cassette.image!.size.height
var scale = cassetteWidth / cassetteImageWidth
var spindleWidth = cassetteWidth
var spindleHeight = cassetteImageHeight*scale
var spindleX = 0 as CGFloat
var spindleY = (cassetteHeight - spindleHeight)/2
if spindleHeight > cassetteHeight {
scale = cassetteHeight / cassetteImageHeight
spindleHeight = cassetteHeight
spindleWidth = cassetteImageWidth*scale
spindleY = 0 as CGFloat
spindleX = (cassetteWidth - spindleWidth)/2
}
let spindle = UIImageView(image: UIImage(named: "spindle"))
cassette.addSubview(spindle)
spindle.frame.origin.x = spindleX + (posX*scale)
spindle.frame.origin.y = spindleY + (posY*scale)
spindle.frame.size.width = spindle.image!.size.width*scale //299.0
spindle.frame.size.height = spindle.image!.size.height*scale //299.0
return spindle
}
Programmatically (without storyboards), I am trying to remove one set of constraints and create a new set whenever the device is rotated. The code I use to remove the constraints executes without error but does not actually remove the constraints. This results in a layout error when the new set of (conflicting) constraints is added.
I am using Xcode 10.1 and Swift 4.2 without storyboards. In my MainViewController, I am creating and placing a UICollectionView named "ticketsView" from functions called by viewDidLoad(). After addChild() and addSubview(), I call a function named applyTicketsViewConstraints() which determines the current orientation of the device and then (is expected to) remove any existing constraints and apply the appropriate set of constraints for the current device orientation.
I override viewWillTransition:toSize:withCoordinator() which calls the same applyTicketsViewConstraints() function from within the coordinator closure whenever the device is rotated.
At app startup, the constraints are correctly applied based on the initial device orientation. The first time the device is rotated 90 degrees, the display also successfully changes, but any rotation after that result in a "[LayoutConstraints] Unable to simultaneously satisfy constraints" error message.
I have tried looping through the existing ticketsView constraints and removing them one at a time and I have tried removing them all at once - but neither approach results in the actual removal of the constraints. They both appear to run successfully, but the constraints are still in place after they complete.
func createTicketCollectionWindow() {
ticketsLayout = UICollectionViewFlowLayout()
ticketsVC = TicketCollectionController(collectionViewLayout: ticketsLayout)
ticketsVC?.collectionView?.backgroundColor = UIColor.darkGray
ticketsView = ticketsVC!.view
ticketsView.translatesAutoresizingMaskIntoConstraints = false
addChild(ticketsVC!)
self.view.addSubview(ticketsView)
applyTicketsViewConstraints()
}
func applyTicketsViewConstraints() {
let safe = self.view.safeAreaLayoutGuide
let deviceSize = UIScreen.main.bounds.size
for individualConstraint in ticketsView.constraints {
print("TicketViewConstraints: Removing Constraint: \(individualConstraint)" )
// ticketsView.removeConstraint(individualConstraint)
}
ticketsView.removeConstraints(ticketsView.constraints)
// self.view.removeConstraints(ticketsView.constraints)
if deviceSize.width < deviceSize.height {
print("TicketsView Constraint: Device is in PORTRAIT orientation!")
ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width).isActive = true
ticketsView.trailingAnchor.constraint(equalTo:safe.trailingAnchor, constant: 0 ).isActive = true
ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0).isActive = true
ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.75).isActive = true
} else {
print("TicketsView Constraint: Device is in LANDSCAPE orientation!")
ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width).isActive = true
ticketsView.trailingAnchor.constraint(equalTo:safe.trailingAnchor, constant: 0 ).isActive = true
ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0).isActive = true
ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.65).isActive = true
}
ticketsView.layoutIfNeeded()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (_) in
self.applyTicketsViewConstraints()
self.ticketsVC?.collectionView.reloadData()
}) { (_) in
self.ticketsView.layoutIfNeeded()
}
}
I am expecting that the 4 constraints I am creating in the code above will be removed so that I can recreate them with different values. I can't seem to find a way to remove them (that works). The 2 lines above that are commented out in the code above are there to show that I have also tried them (unsuccessfully).
You can try
var portrait = [NSLayoutConstraint]()
var landscape = [NSLayoutConstraint]()
let porCon1 = ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width)
let porCon2 = ticketsView.trailingAnchor.constraint(equalTo:safe.trailingAnchor, constant: 0 )
let porCon3 = ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0)
let porCon4 = ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.75)
portrait = [porCon1,porCon2,porCon3,porCon4]
let landCon1 = ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width)
let landCon1 = ticketsView.trailingAnchor.constraint(equalTo:safe.trailingAnchor, constant: 0 )
let landCon1 = ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0)
let landCon1 = ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.65)
landscape = [landCon1,landCon2,landCo3,landCon4]
func setPortrait(_ por:Bool) {
portrait.forEach { $0.isActive = por }
landscape.forEach { $0.isActive = !por }
}
Could be also
NSLayoutConstraint.deActivate(portrait)
NSLayoutConstraint.deActivate(landscape)
if por {
NSLayoutConstraint.activate(portrait)
}
else {
NSLayoutConstraint.activate(landscape)
}
I believe that you have to work with activate/deactivate constrains, example:
Create variables for them and set the constraints values in your setupConstrainsMethod.
var notRotatedYConstraint: NSLayoutConstraint!
var rotatedYConstraint: NSLayoutConstraint!
in viewdidload:
NSLayoutConstraint.activate([notRotatedYConstraint])
In your Function for when the iPhone is rotated.
NSLayoutConstraint.deactivate([notRotatedYConstraint])
NSLayoutConstraint.activate([rotatedYConstraint])
The first 2 answers that were posted combined to give me the answer I needed. #Sh_Khan instructed me to use an NSLayoutConstraint array. #Wurzel pointed out that I should be using the NSLayoutConstraint.activate() and .deactivate() functions to enable/disable the entire group of constraints all at once.
So, the key was to add all of the portrait constraints to one array, add all of the landscape constraints to another array and then every time the orientation changes, I call the deactivate function on both arrays and then re-create and activate the appropriate array.
(The safeAreaLayoutGuide changes when the device orientation changes which is why I was unable to just pre-create both sets of constraints in advance. I also wanted a solution that would work if the constraints were to become more dynamic - for example responding to a user resizing the display area)
My original code now becomes:
func applyTicketsViewConstraints() {
let safe = self.view.safeAreaLayoutGuide
let deviceSize = UIScreen.main.bounds.size
NSLayoutConstraint.deactivate(portraitTicketsViewConstraints)
NSLayoutConstraint.deactivate(landscapeTicketsViewConstraints)
if deviceSize.width < deviceSize.height {
print("TicketsView Constraint: Device Size is in PORTRAIT orientation!")
portraitTicketsViewConstraints = [
ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width),
ticketsView.trailingAnchor.constraint(equalTo: safe.trailingAnchor, constant: 0 ),
ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0),
ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.75)
]
NSLayoutConstraint.activate(portraitTicketsViewConstraints)
} else {
print("TicketsView Constraint: Device Size is in LANDSCAPE orientation!")
landscapeTicketsViewConstraints = [
ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width),
ticketsView.trailingAnchor.constraint(equalTo: safe.trailingAnchor, constant: 0 ),
ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0),
ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.65)
]
NSLayoutConstraint.activate(landscapeTicketsViewConstraints)
}
ticketsView.layoutIfNeeded()
}
}
Note : Not created ViewController in storyboard
I created ViewController programmatically after giving support for safe guard now my problem is that how i can got his height?
I try following thing but no luck
let guide = view.safeAreaLayoutGuide
let height = guide.layoutFrame.size.height
print("\(height)")
print("\(self.view.frame.height)")
height = 812.0, self.view height = 812.0
Is there any other way?
A safeAreaLayoutGuide, which is iOS 11+, is meant for auto layout and to try to get a frame is close to trying to make orange juice from a lemon.
If what you want is to find out is what you can safely use for a frame, maybe try something like this:
Create a transparent view:
let mySafeView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
mySafeView.backgroundColor = UIColor.clear
mySafeView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mySafeView)
// if needed, send this view to the back of the hierarchy
view.sendSubview(toBack: mySafeView)
}
Next, in your constraint-building code attach it to the true anchors:
// create generic layout guides
let layoutGuideTop = UILayoutGuide()
view.addLayoutGuide(layoutGuideTop)
let layoutGuideBottom = UILayoutGuide()
view.addLayoutGuide(layoutGuideBottom)
let margins = view.layoutMarginsGuide
view.addLayoutGuide(margins)
// get correct top/bottom margins based on iOS version
if #available(iOS 11, *) {
let guide = view.safeAreaLayoutGuide
layoutGuideTop.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0).isActive = true
layoutGuideBottom.bottomAnchor.constraintEqualToSystemSpacingBelow(guide.bottomAnchor, multiplier: 1.0).isActive = true
} else {
layoutGuideTop.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
layoutGuideBottom.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
mySafeView.topAnchor.constraint(equalTo: layoutGuideTop.topAnchor).isActive = true
mySafeView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
mySafeView.bottomAnchor.constraint(equalTo: layoutGuideBottom.bottomAnchor).isActive = true
mySafeView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
Finally, get the frame size:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(mySafeView.frame)
}
On an iPhone 6 plus running iOS 11.1 you get:
(0.0, 28.0, 414.0, 716.0)
On an iPhone 6 plus running iOS 9.0 you get:
(0.0, 20.0, 414.0, 716.0)
First off you can't do it in the viewDidLoad. You have to wait till viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myGlobalVars.sceneRect = view.frame
if #available(iOS 11.0, *) {
myGlobalVars.topSafeArea = view.safeAreaInsets.top
myGlobalVars.bottomSafeArea = view.safeAreaInsets.bottom
} else {
myGlobalVars.topSafeArea = topLayoutGuide.length
myGlobalVars.bottomSafeArea = bottomLayoutGuide.length
}
}