So, I have a game that was developed for iPad. It's pretty complex, combination of Unity and native UIKit, a bit of Interface builder, a lot of programmatic view generation, surely many hard-coding sins and so on.
Now I would like to create an iPhone version of this game. I could either go the lengths of redesigning and porting all the graphics, programmatically make them fit arbitrary view sizes and aspect ratios, use Interface builder's autolayout and so on,
OR
I just scale everything down to fit the screen. Which is what I'm trying now. I'm fine with letterboxing.
So, right after my application launched, I scale down the main window, like so:
//scale window down to iPhone 6 size
CGAffineTransform transform = CGAffineTransformMakeScale(0.416666f, 0.416666f);
UIView* v = self.window;
CGPoint origin = v.frame.origin;
v.transform = transform;
v.frame = CGRectMake(origin.x, origin.y, v.frame.size.width, v.frame.size.height);
Works well, looks great, no aliasing or performance problems, but:
touch input doesn't work anymore - I can't click anything! Does anyone have an idea what could be the reason for this and how to solve for it?
The same is true by the way If I replace 'self.window' with 'self.window.rootViewController.view'
Related
I'm making a react-native based app, and I can partially use Swift code by react-native's NativeModule feature.
My app will be used as Split view / Slide over mode in iPad, and I want to know if my app is in the left-side, right-side, or Slide-over.
I could get width and height, also origin (CGPoint) by this code.
let window = UIApplication.shared.windows.first
// Then getting X, Y like this...
window.frame.origin.x
Now my app has only one webview, made by react-native-webview.
So I guess, since the webview is the whole content of the app, and it fills app 100%, so it always returns app.
I want to know, not in webView's perspective, but in app's perpective, the POSITION relative to the screen.
For example, if my iPad width is 1400, and if my app is on the half right side, x should be 700.
And if it is on left side, it should be 0.
I really struggled this, but couldn't find any solution.
FYI, I drawed a diagram for this question.
As I wrote on the above, I tried UIApplication.shared.windows.first.frame.
But it only show the CGPoint of webview, not app relative to the screen.
There is no API for getting 'X' or 'LEFT' in react-native-dimension too.
onLayout also not helpful.
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)
I am generating my levels in SpriteKit via code (in Swift), and its not working out entirely how I'd like. Is there a way to inspect the scene of the simulator at run time. E.g. So I can view how things are being placed outside the visible screen.
Thanks!
Believe me, level editor via code rarely is a good idea. There are exceptions but if you need static levels then you should find another way to defined it.
How can you see your entire scene?
AFAIK you con't with Xcode 7, however you can change how the scene is resize in order to show the full scene inside the view. Depending by the size of your level you can judge whether this is solutions fits your case.
Just open GameViewController.swift and change this
scene.scaleMode = AspectFill
into this
scene.scaleMode = .AspectFit
Now your scene will be compressed to fit the size of the screen. Of course don't forget to restore the original value once you are done.
This question already has answers here:
How to develop or migrate apps for iPhone 5 screen resolution?
(30 answers)
Closed 9 years ago.
I have an app developed and tested on my iPhone 5, but now I would like to make it work on older iphones as 4 and 4s. The functionalities work in the same way, but there are some buttons which do not appear because of the screen resolution.
Is there any way I can make the same .xib to work on different screen resolutions?
a) You can always design your interfaces (.xib) with anchor or elastic coordinates, so if the screen is bigger/smaller, the coordinates are relative to one of the edges of the screen.
b) You can WRAP the entire screen in a UIScrollView and let the user SCROLL to access all buttons.
These are the easiest. a) will give you more headaches. I would go for b) because it's more time efficient.
For b) I also recommend that you TRY the interface in a smaller screen and check that the hidden content is PARTIALLY VISIBLE, so users have a HINT that there's "something down there".
EDIT
This is an example for initializing a viewController with different xib files.
- (id)init
{
int model = ... //some model inspection method;
switch(model) {
case iphone3g:
return [self initWithNibName:#"iphone3g.xib" bundle:nil];
case iphone4:
case iphone4s:
return [self initWithNibName:#"iphone4x.xib" bundle:nil];
... //etc
I would suggest Apple's WWDC videos on auto layout, those will help with using the same XIB. It really depends on your interface and what you're looking to do, there's no one right answer.
You could also create two XIBs, one for 480 height and another for 568, then write in code a bit to ask the device what size it is, and load the appropriate XIB. This can be useful if auto layout just seems to be a big pain with what you're implementing.
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;
}
}