I am working on creating a remote control app on iOS that involves pickers. On the iPhone SE, 3rd gen (iOS 15.6.1) I have been developing on, the pickers are coming out exactly as I intend. However, when I run the app on a simulator for a 3rd gen (16.1.1) iPhone SE, the pickers height is way off. Through testflight, I had a coworker download the app. He confirmed that, on his hardware, the pickers looked off. Here is what the picker is coming out like:
And here is how it looks on my iphone, how I intend it to look:
(My apologies for the different image sizes)
These pickers start as uninitialized class variables. When initializing the pickers, I want their height and width to be equal to the view they are seated in. When debugging this issue, I added two lines. One prints out the height argument I feed the constructor, and the other prints out the size of the picker after being constructed.
print("pickerRect height argument: " + String(Int(pickerFrame.frame.height))) // reading out 80
rolloffPicker = UIPickerView(frame: CGRect(x: 0, y: 0, width: pickerFrame.frame.width, height: pickerFrame.frame.height))
print("picker height: " + String(Int(rolloffPicker.frame.height))) // reading out 162
Now here is whats baffling me... On the physical iPhone SE it prints out 80 on both lines. For whatever reason, in the sim, it prints out the argument as 80, and the initialized size as 162. I have also tried constructing the CGRect before passing it in, the CGRect readsout 80 after being constructed, but once fed into the picker the size shoots up to 162 again. I have not touched the rolloffPicker variable until this line, and don't have any autolayout going with the picker.
My Mac is on Ventura 13.0.1, XCode is on Version 14.1. The physical iPhone was on 15.6.1, and the sim is on 16.1.1. When I updated the physical iPhone to 16.1.2, the pickers broke! Clearly this issue is in relation to the new update? What changed, and how might I work around it?
Update: Can't believe I missed this, but I am getting this message in the console when constructing the picker:
2022-12-09 19:35:14.434387-0700 Remote Control[645:22988] -[UIPickerView setFrame:]: invalid size {107, 72} pinned to {107, 162}
Update 2:
I tried to add constraints to the picker:
rolloffPicker.translatesAutoresizingMaskIntoConstraints = true
rolloffPicker.widthAnchor.constraint(equalToConstant: pickerFrame.frame.width).isActive = true
rolloffPicker.heightAnchor.constraint(equalToConstant: pickerFrame.frame.height).isActive = true
Now I am getting warnings, pickers size is way off...
AHHHH I fixed it! I changed
rolloffPicker.translatesAutoresizingMaskIntoConstraints = true
to
rolloffPicker.translatesAutoresizingMaskIntoConstraints = false
while keeping the constraints. Why this broke between iOS 15 and 16 is an enigma to me, hopefully this can help someone else out in the future.
Related
since the release of the new macOS Moterey operating system (12) my MacCatalyst application does not load some views at startup until I resize the window or change focus.
If I look at XCode's Debug View Hierarchy these views are not present.
If, on the other hand, I use the classic debug (while they are not shown) it turns out that they have the correct frame (origin and size), superviews and window.
To show them I have to resize the window (manually, using the mouse) or remove the focus from the app (for example by clicking on another window and then clicking the app window again).
Only after doing one of these two things are the views loaded.
Has anyone experienced the same behavior?
The application is developed in Swift only with UIKit and various Storyboards (without SwiftUI).
Thanks a lot to everyone.
I understand what happens.
If it can be useful to anyone ...
I use custom margins for my UIViewControllers. To calculate a margin, I use the minimum width of a scene as a variable.
I take this value in this way:
var minSceneWidth:CGFloat = 400
scenes.forEach { windowScene in
if let w = windowScene.sizeRestrictions?.minimumSize.width {
if minSceneWidth > w {
minSceneWidth = w
}
}
}
Since the latest version of macOS the value "minimumSize.width" seems to be also "0" and this is not good for the calculation of my margins.
Thank you all.
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)
So I have a UIDatePicker in my app.
When I run my app on the simulator this is displayed, with only one complete row being displayed either side of the selected time.
When I deploy the app to my iPhone, this is displayed, with two complete rows being displayed either side of the selected time.
This error is returned when running on the real device:
[UIPickerView setFrame:]: invalid height value 196.0 pinned to 180.0
This error, and other similar questions lead me to believe I shouldn't be able to change the height of the UIDatePicker.
My question is, is it possible to produce a UIDatePicker like the one being displayed in the simulator on real devices and if so, how?
Thanks,
Liam
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)