Is there any way to judge the iPhone is iPhoneX series(iPhone X XR XS XSmax)?
My way is:
#define iPhoneXSeries (([[UIApplication sharedApplication] statusBarFrame].size.height == 44.0f) ? (YES):(NO))
Is there any hidden trouble?
Or is there any better way?
I need to know because iPhoneX series statusbar's height are immutable but other iPhone's statusbar's height can change
So you don't really need to know whether your app is running on an iPhone X-series phone at all — what you're really trying to find out is whether the status bar height can change. And I'll be that you don't really even care about the status bar so much as you want to know where you can put content in your view so that it will always be unobscured by the status bar and other system objects. Whether that's the case or not, you should make sure that you're asking the right question. Don't rely on the device model to tell you about features, and don't rely on particular features to tell you the device model.
iOS usually gives you a way to find out about the features you need. If your goal is to keep your content visible, you should use UIView's safeAreaInsets property and also the safeAreaInsetsDidChange() method, which the system will call when the safe area changes (e.g. when the status bar height changes). You can then adjust your content to fit the new safe insets. Building your app this way means that you don't have to worry about your app breaking on new device models that have feature sets you don't expect, and you don't have to worry about future iOS updates undermining your assumptions.
Try this:
let size = UIScreen.mainScreen().bounds.size
print("Your device size: \(size)");
if size.height == 814 {
print("This is an iPhone X")
}
Related
Looking for a clean solution to laying out UI programmatically and having it look right on every device. I've tried extending CGFloat to scale numbers depending on the device
extension CGFloat {
func scale() {
// Modifies self by multiplies by the ratio between the initial screen size and the desired screen size
}
}
// usage
view.widthAnchor.constraint(equalToConstant: 20.scale())
I've also tried just creating two different sets of constraints for iPhone and iPad and activating them depending on which device the app is being run on, but this seems unnecessarily verbose.
How can I layout my UI so that it will work on all devices without hacky workarounds. Are there certain types of constraints I should be avoiding? (for example, instead of setting width/height constants, set them to a multiple of the screen's (or some other view's) width/height?)
EDIT: I don't want to over-explain my own situation, so let me re-ask the question. What are some best practices for setting constraints in an application designed for both iPhone and iPad. Is it bad practice to just check if the device is an iPad, and if it is, have constraints just for iPad, and, if not, have constraints for just iPhone.
Your question, as-is, cannot be answered...
Wha does your app do? If it's a photo slide-show, constrain an imageView to the full view and set its content mode to scale-fit. Voila! It "looks good" on all devices!
If your app is more complex than that, you will likely need to make use of all the types of constraints: elements relative to each other; equal to each other; relative/equal with constant adjustments; relative/equal with multiplier adjustments; etc. And you may want different layouts (not just different sizing) based on device+orientation, in which case you'll also want to take advantage of size-class-variations.
In addition, getting an app to "look right on every device" involves much, much, MUCH more than applying constraints.
Should the app use a Tab Bar?
A Navigation Bar?
A combination of them?
Neither?
Should it use text-buttons or image-buttons?
Should it adjust for accessibility and dynamic fonts?
Might it even have different functionality when running on a small screen vs a large screen?
In general, your first step should be hand-drawing every screen and UI element you expect to have - including the activity "flow" - with variations for sizes and orientations, so you are designing the best UI/UX from the beginning. At that point, you begin actual UI construction.
Keep in mind, there are people who can't produce a "Hellow World" app but make a very good living as "App Designers."
Having said all that, though... one approach you almost certainly should not take is:
view.widthAnchor.constraint(equalToConstant: 20.scale())
EDIT
Here is a quick example - based on this article: https://www.raywenderlich.com/1343912-adaptive-layout-tutorial-in-ios-12-getting-started
A simple weather app - constraints set so it looks similar on all iPhone and iPad models:
But, what happens when you rotate the phone?
Little, tiny cloud doesn't look so good. Add trait-variations / size-classes, and we can get:
And, to try and answer your edited question of: "Is it bad practice to just check if the device is an iPad..."?
Yes. It's recommended to design for trait-variations / size-classes so your app will look the way you want in all configurations (hopefully future-proof for the next device that comes out as well).
Is it bad practice to just check if the device is an iPad, and if it
is, have constraints just for iPad, and, if not, have constraints for
just iPhone.
If you're planning for Split-View support, that is definitely a no-go. The code for achieving and handling this is always going to be slightly verbose. There are no one-liners here to achieve what you want.
In your -[UIViewController viewDidLoad] method, you'd want to setup your views with the base trait collection available to the controller's view. This is your starting point.
As and when your App's windows are resized by the OS (imagine going for a fullscreen app in landscape mode to a split-sized app, occupying one-third of the screen): the -[UIViewController traitCollectionDidChange:] method is called on your View Controller. This is where you update your layout constraints.
You can, and should, encapsulate your layout logic in a single method and call it from -[UIViewController viewDidLoad] as well as -[UIViewController traitCollectionDidChange:].
In such a method, I do not recommend checking if the host device is an iPad. You specifically want to look at the active trait collection's horizontalSizeClass & verticalSizeClass properties to determine the values for your layout constraints.
For further information, I suggest you go through the following documentation:
1. https://developer.apple.com/documentation/uikit/uitraitcollection?language=objc
2. https://developer.apple.com/documentation/uikit/uitraitenvironment/1623516-traitcollectiondidchange?language=objc
The 2nd link has a simple example on how to check if you need to update your constraints.
I usually first get the size of the screen and then use percentages of the width/height for the frames
height = view.bounds.height
width = view.bounds.width
mybutton.frame = CGRect(x: width * 0.05, y: height * 0.02, width: width * 0.3, height: height * 0.1)
it's all start from a lot of warnings about the width of collectionView cells,
That was written to console every time I press home button.
I thought it's something with size of the cells and tried to fix it.
Only after some testing, I notice that viewDidLayoutView is called with wrong view.bounds, and that make the collectionView cell bigger(in width) than collection view.
For now, my fix is to check if app is on background state and ignore viewDidLayoutView.
why it's happen only in iPad and not on iPhone ?
it's whirred that only now I saw this happening. it's something new in iOS ?
what is the right way to handle this ? I don't use auto-layout
its calling with wrong bounds and I don't want to calculate all cells frames just for the user to return to the same orientation.
I feel like I'm missing something very basic here OR there is some change on iOS that I'm not aware.
why it's happen only in iPad and not on iPhone ?
Because when you click Home on an iPad and the app goes into the background, the runtime takes two snapshots, one in each orientation, to use in the App Switcher interface (and in preparation for when the app comes back to the front). That means it has to rotate the app, which triggers layout, and so your viewDidLayoutSubviews is called.
it's whirred that only now I saw this happening. it's something new in iOS ?
what is the right way to handle this ? I don't use auto-layout
its calling with wrong bounds and I don't want to calculate all cells frames just for the user to return to the same orientation.
I feed like I'm missing something very basic here OR there is some change on iOS that I'm not aware.
Well, iPads have been behaving like this for quite a long time. It isn't my job to explain why you haven't noticed. Basically you should not be doing anything time-consuming in viewDidLayoutSubviews.
for now, my fix is to check if app is on background state and ignore viewDidLayoutView
That's perfectly reasonable. I do the same sort of thing. For example (this is a different method, but it's the same idea):
override func viewWillTransition(to size: CGSize, with tc: UIViewControllerTransitionCoordinator) {
if UIApplication.shared.applicationState == .background {
return
}
// ...
}
Be warned, however, that if you are doing manual layout and you don't do it when the app when goes into the background, your App Switcher snapshots may look wrong. Also, the fact that you are not using autolayout is a red flag. You should probably use it.
when first entry, the result like this
App into the background and back, the orientation is normal
this is the code for the ViewController:
DDYLoveAutoHallViewController *autoHallViewController = [[DDYLoveAutoHallViewController alloc] initWithEvent:room.currentEventID type:AHRoom_System];
[self presentViewController:autoHallViewController animated:YES completion:nil];
i don't know why, the project is a litter old, it support iOS5 before.
Without understanding on how do you manage layouts it is difficult to give the exact reason, caused you problem, but, i can suggest you couple of little advices:
try to re-check orientation in - (void)viewDidLayoutSubviews, and you can re-position things from there. (probably it will work and solve your problem, but it is not very clean solution, because may be called a few times at time, knowing this you can make it cleaner)
play generally with the methods of layout-lifecycle, to detect where are things is going wrong. Dive deeper in modern way to layout on iOS. (better solution)
Without more details regarding your specific problem, and how you are detecting and adjusting the orientation, it is hard to understand the exact problem, but here are some observations I have made, working with iOS 5.x based (and earlier), apps, that might point you in the correct direction:
[1] Earlier versions of iOS set some defaults, before actually interrogating the hardware to determine orientation and app frame size information.
a) Initially orientation is set to PORTRAIT - along with portrait orientation frame dimensions - during initialization. (This is WRONG if you device is in landscape orientation - so DON’T USE this initial info). (left over from earlier “iPhone only days” - I guess)
b) This continues to be incorrect at the “ViewDidLoad” and “applicationDidBecomeActive” timeframes (at least for my app - possibly - depends on loading time etc…)
c) The correct orientation is yielded later, via the “didChangeStatusBarOrientation method invocation. You can use this information with Window.frame.size information, to display the correct image with the correct size.
This is effectively the trigger to indicate the orientation request will now be correct.
One stategy might be to : Don’t try to display anything until the “didChangeStatusBarOrientation” message has been received.
[2] Non-Code Solution: (using Settings : Supported interface Orientations)
a) If the app is designed to always work in the Landscape orientation, make sure this is reflected in the app settings/info.plist. I would suggest only allowing 1 “supported interface Orientations”, that being Landscape, and all views would reflect the landscape size and orientation.(if your code is not specifically setting or doing anything with orientation, then this might be a solution by setting everything to "landscape only". Otherwise this option probably won't help.)
[3] Last Solution:
a) If you can change the base iOS version higher, the iOS6 and greater versions initialize the orientation and frame sizes earlier in the initialization cycle, so the problem may just disappear due to this.
Here is some of the code used to discover this, along with output below (using an original ipad as a sample, in this case…running iOS 5.1.1 - started in landscape and orientation was unchanged)
-(void) OrientationAndScreenSizeHELPER : (NSString *)fromObject
{
//from:
// NSLog(#"%s:%d someObject=%#", __func__, __LINE__, someObject);
CGRect appFrame = [[UIScreen mainScreen ]applicationFrame];//using frame so status bar is not part of calculations.
appFrame = [[self.viewController view]frame];//using frame so status bar is not part of calculations.
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
NSLog(#"orientation[%ld] at time[%#] width[%ld] height[%ld]",
(long)orientation,
fromObject,
(long int)appFrame.size.width,
(long int)appFrame.size.height);
}
**Output:**
orientation[1] at time[ViewDidLoad Orientation] width[768] height[1024]
orientation[1] at time[applicationDidBecomeActive] width[768] height[1024]
orientation[3] at time[didChangeStatusBarOrientation] width[1024] height[768]
I just noticed the iOS 6/7 Delta property found under the UIView's structs layout.
What is this for and why is this missing from AutoLayout?
Note: I noticed this question a while ago, but I'm only posting my answer now because the NDA has been lifted
Why does it not appear for AutoLayout?
As you may have noticed, iOS 7 brings about a whole new look. The look of UI elements have changed, but also so have some of their sizes (or metrics in general). This can make interface design to accommodate both iOS 7 and it's predecessors a bit of a pain.
Apple's official line is to use AutoLayout to solve this; this should take a lot of the hassle out of laying out UI elements for you. Sometimes incorporating this is not easily done, especially if you must still support iOS 5 for business reasons, or your interfaces are managed in a way that makes implementing AutoLayout difficult. As such, Apple seems to have provided a way to make your job a bit easier if you fall into this niche category, and they've called this iOS 6/7 Deltas.
Ok then, what does it do?
Whilst the label in Interface Builder is a bit unclear as to what 'Delta' means in this context, the code contained in the .xib file that corresponds to this feature is a bit more clear:
<inset key="insetFor6xAndEarlier" minX="-50" minY="-100" maxX="-50" maxY="300"/>
The key name insetFor6xAndEarlier explicitly states what this does; you can provide alternative insets for UI elements when run on iOS 7's predecessors. For example, the above defines the following delta change:
x: 50
y: 100
width: -100
height: 200
Whilst the values stored in the .xib doesn't correspond to the quoted values directly, there is a correlation between them.
x: -minX
y: -minY
width: minX + maxX
height: minY + maxY
The images below shows this change visually. It's quite an extreme example, but it's to demonstrate its ability. I would only expect in practice to have delta changes of only a few pixels.
You may notice that the values are the inverse for the iOS 6 view; this is because the deltas are relative to the type of view you're working with. If you're editing for iOS 6, the deltas there are in order to transform the element correctly for iOS 7 (the reverse of the example above).
In order to view the different styles, you can change the way Interface Builder presents it based on the OS it would be running on. This is contained within the File Inspector->Interface Builder Document (1st tab on the right bar), as so:
Does this exist if I like to code my interface by hand?
Not directly, but you can easily achieve the same effect by having conditional checks on OS version within your code, and setting the correct position/size accordingly. The delta ability exists in Interface Builder because there would be no straightforward way to have conditional positioning without having code to do it, and the point of Interface Builder is to get a much code out of the way as possible for UI.
Overall...
Apple strongly recommend that you use AutoLayout, it makes your life easier in most cases. If you can't use it (for reasons mentioned above), deltas provide you with the flexibility to position your UI elements appropriately, based on the current OS's metrics, without the need to manually reposition them in code. A good example is to adjust for the lack of status bar, but there are plenty of other use cases.
Naturally, if you're only developing for iOS7 and above, you don't need to know this feature/won't discover it. Only if you need to have iOS6 devices running your application when built with the iOS7 SDK, without autolayout, do you need deltas.
At the time of writing (21st August), I can't find any documentation regarding this feature, nor any mentions in the WWDC material. I've had a play around, and after a bit of research, that is what I've discovered.
This actually refers to the Delta between layout position from iOS6 to iOS7.
In iOS7, some views can hide the status bar or have it transparent and, in effect, it is overlaid on top of your view. So if you put a UI element at (0.0, 0.0) on iOS6, it will appear below the status bar, but on iOS7 it would appear partially covered underneath the status bar. So in that case you would want a delta that matches the status bar height (20.0 points) so that the layout looks the same in iOS6 and iOS7.
I believe this isn't needed if you use autolayout, but of course, then you lose iPad1 support, which many of us aren't willing to concede at this point in time.
I know this is already been answered, just adding a small variant hoping it could also help those who don't use auto layout and still want to support iOS 6.1 and earlier versions.
Read this Apple's Transition Guide - Supporting earlier version
Choose 'View as' to 'iOS 7.0 and Later'
Base UI for iOS 7. For iOS 6 give suitable delta value. Use preview to see how this will render in iOS 7 and iOS 6 device.
Quick steps:
Select each immediate children of root view individually and add 20px to its 'Y' value.
Then, select all immediate children collectively and give delta Y as -20px. You can also do this in batch or individually.
AutoLayout requires at least iOS 6.0. If you want to support iOS 5.0 you couldn't use AutoLayout.
And those deltas are used to help you adjust the view position on different iOS version(mainly iOS 7 and iOS version lower than 7).
I use those value to help me like this picture.
To see iOS 6/7 Delta in action, I will demo with a SegmentedControl that appears properly on both iOS 6 and iOS 7 devices.
First, select your .Xib or ViewController in Storyboard. Uncheck Use Autolayout and select "View as iOS 7 and later"
In the Interface Builder canvas, place your SegmentedControl so that its origin.y is 20. In iOS 6/7 Delta, choose -20 for DeltaY
This will make your the SegmentedControl laid below the Status Bar in both iOS 6 and iOS 7 devices
Another useful quotes from Developer’s Guide to the iOS 7 Status Bar
Deltas can be set individually for each view and work as you would
expect. If your storyboard or nib is set to view as iOS 6, then
setting the deltas will cause that view to be shifted and/or resized
by the set delta amount when run in iOS 7. Alternately, if your
storyboard or nib is set to view in iOS 7, then the deltas will be
applied when run in iOS 6
If you are using AutoLayout, then Delta is not available.
Try this (tested in iPhone 4s running iOS6):
- (void) viewWillLayoutSubviews {
//iOS 6 workaround offset
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7) {
self.view.clipsToBounds = YES;
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenHeight = 0.0;
screenHeight = screenRect.size.width;
CGRect screenFrame = CGRectMake(0, -20, self.view.frame.size.width,self.view.frame.size.height+10);
self.view.frame = screenFrame;
}
}
I am working on my app to customize button accordingly. However once I change my button accordingly, now it has became irresponsive. You could see my code as follows. By the way, this button still works when I run on iPhone 4. It is really strange!
once I change my button position it works, but it wont work the position I want!
float screenSizeHeight=[UIScreen mainScreen].bounds.size.height;
if(screenSizeHeight==568)
[positionButton setFrame:CGRectMake(184,280,77,30)];
if(screenSizeHeight==480)
[positionButton setFrame:CGRectMake(184,240,77,30)];
Hand Bag button is not working!
Check your condition :
if(screenSizeHeight==568)
In both cases you have used screenSizeHeight == 568 so replace one with screenSizeHeight = 480
Hope it helps you.
Check your Default-568h.png image get added or not. If it isn't added , you mainscreen returns bounds as 320x480.
Note : 1) If you rotate device , check your main screen bounds. It may lead to confusion. You can try Better way to use it.
2) This Default-568h.png is only allowed when building an app using Xcode 4.5 and the iOS 6 SDK
3) When you ask a UIScreen for it's Bounds you get the bounds of the screen, which is the whole device screen. (the status bar is part of the screen)