How to make sure uiview looks same in different ipads without using constraints - ios

in below example i have mentioned
-view (UIView which i will be displaying in both iPads).
-parentView (container UIView that will be same as iPads resolution).
-parentViewOfIPadInWhichViewHasMade : this is parentview of iPads in which view has been saved.this will be used when displaying view in different iPads.
function is i am saving view in iPad mini and saving it will store its frame information on server and when open app from iPad pro it will fetch those data from server and display view in iPad pro.
this is my expected output if view has been resized to fullscreen of iPad Pro and Fetched From iPad Mini
iPad Pro 12.9 inch :
view.size (x : -25.0, y : -25.0, width : 1416.0, height : 874.0)
parentView.size (x : 0.0, y : 0.0, width : 1366.0, height : 824.0)
iPad Mini :
view.size : (x : -25.0,y : -25.0, width : 1074.0, height : 618.0)
parentView.size : (x : 0.0, y : 0.0, width : 1024.0, height : 568.0)
to get this i have wrote below code in view did load.
viewX = parentView.frame.width * view.origin.x / parentViewOfIPadInWhichViewHasMade!.width
viewY = parentView.frame.height * view!.origin.y / parentViewOfIPadInWhichViewHasMade!.height
viewWidth = parentView.frame.width * view!.frame.width / parentViewOfIPadInWhichViewHasMade!.width
viewHeight = parentView.frame.height * view!.frame.height / parentViewOfIPadInWhichViewHasMade!.height
note : can't fix size of view or use Constraint.

I can suggest a couple of ideas (before coding..):
1) your graphic/creative design must precisely consider the layout as a UX point of view. After this huge work you can start coding.
2) You can of course use code without using constraints. Asa programmer you cn prefer to do math instead of using mouse. But be careful: Using constraints is much more flexible and you can mix code too.
3) a pervasive and deep approach can be to implement layoutSubviews in every view..
example:
override func layoutSubviews() {
super.layoutSubviews()
self.titleLabel?.sizeToFit()
self.subTitle?.sizeToFit()
self.callLabel?.sizeToFit()
if(self.subTitle?.text == nil) {
self.callLabel?.center.y = (self.titleLabel?.center.y)!
}
}
but honestly for basic needs, better to use constraints, and eventually add an IBOutlet so you can control in code, for example height in custom view:
final func setH(Hconstraint: NSLayoutConstraint!){
var h = Hconstraint!.constant
if (.... add your test based on models, orientation...){
h = H1
}else{
h = H2
}
Hconstraint.constant = h // re-apply height.
self.setNeedsDisplay() // to trigger redraw... seems needed in some cases...
}
(you can also add this code in controller.. but better to put it in custom view, so the logic is there..)

we need to use scale for this problem.
if we have saved view in ipad pro and opening in ipad mini than we need to set parentview's height and width from ipad pro parentview. and than scale down it to ipad mini parentview and calculate x and y cordinate change.

Related

How do I add constraints so that my view's dimensions do not change when the orientation changes?

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)
}

UIbutton misplaced in simulator

Here I have to implement the UIView for both the screens iPhone 5 and 6. So I used the screeHeight to integrate the UIView. But the button in the view is misplaced in iPhone 5s simulator and works fine in iPhone 6 simulator Here is the code for that. Whatever I changed the values in x and y remains the same top position.
if screenHeight == 568{
self.viewFareEstimate.frame = CGRect(x:0, y:150, width:359, height:40)
}
The screen size of Iphone 5 is 320 × 568, but your width is more than 320.
Try this
if screenHeight == 568{
self.viewFareEstimate.frame = CGRect(x:0, y:150, width:320, height:40)
}
Hi #imac for good programming practices try to avoid to give constant values to frame.
For your refrence height for iPhone 5 is 568.0f & 6 is 667.0f are different,
&
Whenever you want size width full then try to give frame like this,
self.viewFareEstimate.frame = CGRect(x:0, y:150, width:self.view.frame.size.width, height:40);// Manage width by + or - constant value now.
In your case check that if you are giving constraint from storyboard already then it should work with setting frame only.

layoutIfNeeded sequence acts differently on iPad versus iPhone; how to fix?

Fire up Xcode and for clarity build only to say 9.3, universal app. So, compare 9.3 iPads with 9.3 iPhones. Build to both simulator and devices - issue exhibits on both.
The app rotates in all four directions.
Have a typical situation where you do something like this...
#IBOutlet weak var doorHeightPerScreen: NSLayoutConstraint!
var heightFraction:CGFloat = 0.6
{
didSet
{
if ( heightFraction > maxHeight ) { heightFraction = maxHeight }
if ( heightFraction < minHeight ) { heightFraction = minHeight }
let h = view.bounds.size.height
spaceshipHeightPerScreen.constant = h * heightFraction
self.view.layoutIfNeeded() // holy! read on....
}
}
Notice the layoutIfNeeded() after the change to the constraint.
Continuing the typical example, you will have something like
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
heightFraction = (heightFraction)
// use "autolayout power" for perfection every pass.
// now that basic height/position is set,
save/load reactive positions...
position detail stuff...
}
Check it out ... I was doing this all day and only happened to use iPhones.
Interestingly you do not need the layoutIfNeeded call:
#IBOutlet weak var doorHeightPerScreen: NSLayoutConstraint!
var heightFraction:CGFloat = 0.6
{
didSet
{
if ( heightFraction > maxHeight ) { heightFraction = maxHeight }
if ( heightFraction < minHeight ) { heightFraction = minHeight }
let h = view.bounds.size.height
spaceshipHeightPerScreen.constant = h * heightFraction
}
}
Works fine.
However at the end of the day I put it on some iPads and .... everything broke!
Whenever you rotate landscape/portrait, problems.
After a head scratch, I realized that incredibly you do need the layoutIfNeeded call, on iPad. That's on the identical OS.
Indeed the behavior exhibits regardless of OS version. And it exhibits for ALL iPhones / ALL iPads.
#IBOutlet weak var doorHeightPerScreen: NSLayoutConstraint!
var heightFraction:CGFloat = 0.6
{
didSet
{
if ( heightFraction > maxHeight ) { heightFraction = maxHeight }
if ( heightFraction < minHeight ) { heightFraction = minHeight }
let h = view.bounds.size.height
spaceshipHeightPerScreen.constant = h * heightFraction
self.view.layoutIfNeeded() //MUST HAVE, IN IPAD CASE!!!!!!
}
}
To me it is incredibly troubling that they would work differently.
What I'm wondering is, is there perhaps a setting somewhere to make them work the same? Could it be my fault somehow?
Are there any other know differences between the two - or indeed is it "known" that there are a few bugs like this?
I can't think of anything odd or unusual I did anywhere, except the whole app has override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { return .All } in the first view as is normal if you want to turn the device upside down; I doubt it's related. Other than that it's a very "clean" fresh app.
It gave me a glitch-in-the-matrix feeling - it was terrifying.
What could cause this?
Per RobM's question, the SimulatedMetrics settings (Attributes tab) on the initial ViewController are...
General scheme of the app: the first scene "General" is full-screen, the size of the device. There's a container to "Live" which is the same size (using "Trailing" etc/ constraints as zero all round). In Live, there's a container view "Quad" which indeed is also fully sized to "Live," so it's also fullscreen. Quad:UIViewController exhibits the issue I describe. Quad contains various objects (images, custom controls etc) which sit around on the view. When the app launches, all is fine.
On rotation of the device (or similar): just after the change to the constraint (I don't know if that's relevant): the layoutIfNeeded call IS needed for iPads (all iPads), but is NOT needed for iPhones (all iPhones). The behavior is identical in the simulator and on devices.
Another example
I found another astounding example of this.
In a UICollectionView, custom cells (just simple static sized cells). If you happen to change a constraint (imagine say resizing an icon or product shot within the cell).
On iPad you do have to be sure to readjust in layoutIfNeeded or it will not work on the first appearance of the cell.
Whereas on iPhone it definitely behaves differently: it will "do that for you", before the first appearance of the cell, if you happen to omit it.
I tested that on every iPad and every iPhone. (Also, the unusual behavior exhibits exactly on devices or simulators: simulator makes no difference.)
I'm not able to reproduce what you're seeing; it would be nice to see a complete example. In my mockup I configured a view controller with a view having a single subview, with a constraint to control the subview height. I altered the subview height constraint in viewDidLayout based on the view size. The behavior was identical for both iPhone and iPad, and worked sans calling layoutIfNeeded on any view.
That said, I think you're changing subview constraints once the view has completed its layout - yes? I think the better way to do that would be to layout your subviews ahead of that, via viewWillTransitionToSize:withTransitionCoordinator:.
func viewWillTransitionToSize(_ size: CGSize,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
This way auto layout for the view hierarchy can complete in a single pass. This method is only called when the view is changing size, so it won't be called when the view is first loaded; you'll have to set up your initial constraints somewhere else - since they're dependent on view size perhaps you can use viewWillAppear.
Alternatively, (and possibly more correctly), subclass your view controller's view and override updateConstraints. This is the most appropriate place for changing your constraint constants.
Finally, in your property setter, you shouldn't ever call view.layoutIfNeeded(). If anything, you can set view.setNeedsLayout() so that layout happens in the next runloop iteration, and picks up ALL changes that may need to be represented.
The default simulated metrics size is “inferred”, which (if the scene isn't the target of a segue or relationship) gives you a 600x600 view, which doesn't correspond to the screen size of any iOS device. You changed the simulated metrics size at some point to “iPhone 5.5-inch”, probably to match the size of your main test device.
When a view is loaded from a storyboard (or xib), it's loaded at the size it had in the storyboard. It may then be resized by its container (either the UIWindow if it's the root view of the app, or by its superview if it's the root view of a contained view controller).
In your case, it sounds like your main test device's screen has the same size as the root view in your storyboard, so the test device doesn't have to run as much layout as you might expect.
When you use a test device whose screen size differs from your root view's size in the storyboard, the test device has to do more layout.
I didn't try to reproduce your problem, so I'm not claiming that this is a full explanation of what you're seeing. There may well be an iOS bug involved. Nevertheless, this should explain why your app behaves differently on different devices. I believe this is also why Apple chose the default inferred size of 600x600: since no device screen is that size, all devices will have to do the same amount of layout.

iOS 8 - UIWindow Orientation Is Always Portrait

I am updating an iOS 6 app to iOS 8 (iPad), and any new UIWindows that I create are always showing up in Portrait mode. The app originally supported both the Portrait and Landscape orientations but now will only support Landscape.
I've changed the supported orientations in the project file to Landscape Left and Landscape Right. The entire UI shows up in landscape, as expected, but when I create a new UIWindow, it shows up in portrait. The new UIWindow's frame matches the screen's frame exactly, so I can't imagine why/how it is showing up in portrait mode.
The following code is what I am using to create and show the new UIWindow, which acts as a modal:
var modalWindow:UIWindow = UIWindow(frame: self.view.window!.bounds)
modalWindow.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.66)
modalWindow.hidden = false
modalWindow.windowLevel = (UIWindowLevelStatusBar + 1)
modalWindow.addSubview(customView)
modalWindow.makeKeyAndVisible()
I've been struggling with this for a few hours; shouldn't the UIWindow be in landscape mode by default since the app only supports the Landscape orientation?
I would greatly appreciate any advice on how to resolve this issue.
EDIT:
I just created a new test app that only supports Landscape Left and Landscape Right, and the issue occurs there as well. Is this a bug? I can't seem to understand why the UIWindow would think the app is in Portrait mode when it's in Landscape mode.
EDIT (July 2, 2015)
This answer breaks in iOS 8.3+ and possibly previous versions of iOS 8. I DO NOT recommend that anyone uses it, especially since it is not guaranteed to work in future iOS versions.
My new solution uses the standard presentViewController method of a UIViewController. I initialize a UIViewController, add my custom modal View as a subview of the UIViewController, and then position the modal View using constraints. Works like a charm!
Original Answer
I left this for a while but decided to come back to it today. I ended up rotating the modal window -90 degrees since it is automatically rotated 90 degrees to be displayed in portrait. I also added a small calculation for the modal window's width and height to support both iOS 7 and iOS 8. Here is my new code:
// Get the screen size
var screenSize:CGSize = UIScreen.mainScreen().bounds.size
// Use the larger value of the width and the height since the width should be larger in Landscape
// This is needed to support iOS 7 since iOS 8 changed how the screen bounds are calculated
var widthToUse:CGFloat = (screenSize.width > screenSize.height) ? screenSize.width : screenSize.height
// Use the remaining value (width or height) as the height
var heightToUse:CGFloat = (widthToUse == screenSize.width ? screenSize.height : screenSize.width)
// Create a new modal window, which will take up the entire space of the screen
var modalWindow:UIWindow = UIWindow(frame: CGRect(x: 0, y: 0, width: widthToUse, height: heightToUse))
modalWindow.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.66)
modalWindow.hidden = false
modalWindow.windowLevel = (UIWindowLevelStatusBar + 1)
modalWindow.addSubview(customView)
// Create a -90 degree transform since the modal is always displayed in portrait for some reason
var newTransform:CGAffineTransform = CGAffineTransformMakeRotation(CGFloat(-M_PI / 2))
// Set the transform on the modal window
modalWindow.transform = newTransform
// Set the X and Y position of the modal window to 0
modalWindow.frame.origin.x = 0
modalWindow.frame.origin.y = 0
modalWindow.makeKeyAndVisible()
As stated, this works great on both iOS 7+ and iOS 8+! When working with subviews, note that the modal's width and height values are switched since it's displaying in portrait. So, if you want to center a subview horizontally, use the modal window's height and the subview's width instead of both views' width.

iPhone auto layout images arrangement issue

I want to arrange 3 Images on iPhone & iPad using auto layout.
3 Images should rezise preserving aspect ratio, and width of all 3 images should be same.
Same space from left and right sides for all 3 images.
See Example figure (Figure shows landscape and portrait mode) on this link:
Portrait: http://i.imgur.com/9KVXATE.png
Landscape: http://i.imgur.com/tDjj9K6.png
It is possible programatically getting width and height of screen/view but I want to do it using auto-layouts
Programatically:
//Inside this method
//- (void) didRotateFromInterfaceOrientation:
(UIInterfaceOrientation)fromInterfaceOrientation
//Main_View is the view in which the 3 images are kept
//ImageView1, ImageView2, ImageView3 are 3 image views
ImageView1.frame = CGRectMake(Main_View.frame.origin.x + 3, Main_View.frame.origin.y + 30, ((Main_View.frame.size.width / 3)-4), ((Main_View.frame.size.width / 3) - 4) * (82.0/75.0));
ImageView2.frame = CGRectMake(ImageView1.frame.size.width + 6, Main_View.frame.origin.y + 30, ((Main_View.frame.size.width / 3)-4), ((Main_View.frame.size.width / 3) - 4) * (82.0/75.0));
ImageView3.frame = CGRectMake(ImageView2.frame.size.width + ImageView1.frame.size.width + 9, Main_View.frame.origin.y + 30, ((Main_View.frame.size.width / 3)-4), ((Main_View.frame.size.width / 3) - 4) * (82.0/75.0));
Main_View changes its size(width,height). Then (ImageView1 width) is (Main_View width / 3), It Preserves aspect ratio too.
Programatically it works perfect
This is quite easy to do it with autolayout in interfacebuilder as well. But, there are few things which you might have to sacrifice when using autolayout in interface builder. You would want to arrange items the items in storyboard. The steps to do it are as follows:
Add three views to interface builder, like so,
Select all three pictures and add new constraint in interface builder with the button like this |---|, and select equal width and equal height.
Then, again select all three views and then from the menu select Editor -> Align -> Top Edges
Select the left most view and give it some offset to the left,
Select the right most view and give it some offset to the right,
Select one of the view and give some offset at the top and bottom.
Select second view control + drag it to first and select the horizontal separation and give some constant value and do the same for the second and third view.
Now, the constraints is complete. It could happen that when you select the multiple views at the same time and add some constraint, it might not have been added to one of the views. So might have to resolve this looking error and seeing the constraints for the view, if all those constraints we added is there.
You could keep the constraint that separate the view from the top margin or bottom margin and create outlet for it and then change to suit your need or you could set some parameter in storyboard itself.
Here is the screenshot from my ipad simulator for the horizontal orientation.
And here is for vertical orientation,
I hope I have answered your question. Do let me know if you have something with this.
(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
// i assume here that you have three imageviews img1, img2, img3
float viewWidth = [UIScreen mainScreen].bounds.size.width;
float viewHeight = [UIScreen mainScreen].bounds.size.height;
if (toInterfaceOrientation == UIInterfaceOrientationPortrait){
// your portrait mode code here
}
else{
NSLog(#"bounds are (%f,%f)", viewHeight,viewWidth);
img1.width = viewHeight/3; //i used "viewHeight" because of landscape mode
img2.width = viewHeight/3;
img3.width = viewHeight/3;
// and then set centre of imageview according to your requirement
}
}

Resources