I am working on an app that has a pause button that is sized with autolayouts. This sometimes leads to the correct size of the rect being called in drawRect and sometimes it's {{0.0},{22,22}} (never something else) leading to a very small Pause Button.
- (void)drawRect:(CGRect)rect
{
NSLog(#"%#", NSStringFromCGRect(rect));
// Everything here follows the size of the rect to draw
}
The 22px seems kind of random as that value doesn't appear anywhere in the application. The only thing it reminds me of is that I use a 10px inset for the stuff I draw inside the drawRect (just setting x and y coords, width and height, nothing else on the UIButton) so 42px - 2 * 10px would be 22px.
I set the constraints up in IB as following:
The following emulators (Xcode 5.1.1) / hardware devices work because they gives me scaled output or do not work when the output is 22px:
iPhone iOS 6.1 22px
iPhone Retina 3,5" iOS7.1 47.5px
iPhone Retina 3,5" iOS6.1 22px
iPhone Retina 4" iOS6.1 22px
iPad Mini Retina iOS7.1 22px
iPad 2 iOS 6.1 22px
iPad 2 iOS 7.1 63px
The button is 42px in IB.
These are the constraints when it's OK:
po ((UIView*)self.pauseButton).constraints
<__NSArrayM 0x972e160>(
<NSLayoutConstraint:0x979ca10 V:[PauseButton:0x979c6d0(63)]>,
<NSLayoutConstraint:0x979ca40 H:[PauseButton:0x979c6d0(63)]>,
<NSContentSizeLayoutConstraint:0x961f4a0 H:[PauseButton:0x979c6d0(30)] Hug:250 CompressionResistance:750>,
<NSContentSizeLayoutConstraint:0x961f5f0 V:[PauseButton:0x979c6d0(33)] Hug:250 CompressionResistance:750>
Same point when layout fails (this often throws really weird errors instead):
po [self.pauseButton constraints]
<__NSArrayM 0x7f59920>(
<NSLayoutConstraint:0x7f6f980 V:[PauseButton:0x7f6f720(22)]>,
<NSLayoutConstraint:0x7f6f9c0 H:[PauseButton:0x7f6f720(22)]>,
<NSContentSizeLayoutConstraint:0x7f6f360 V:[PauseButton:0x7f6f720(22)] Hug:250 CompressionResistance:750>
)
po [[UIWindow keyWindow] _autolayoutTrace] is the same for both, something similar to an empty VC where you get AMBIGUOUS LAYOUT on the Window and two LayoutGuides:
*<UIWindow:0x108f537f0> - AMBIGUOUS LAYOUT
| *<UIView:0x109409850>
| | *<_UILayoutGuide:0x109409c10> - AMBIGUOUS LAYOUT
| | *<_UILayoutGuide:0x10940a540> - AMBIGUOUS LAYOUT
The rest is OK.
I have no idea where the magic 22px constraint comes from or why it only appears in some circumstances.
I just fixed it by taking the following steps:
Removing the PauseButton completely from Interface Builder
Adding it again with exactly the same settings
Removing the app from the device I was testing on
The part that concerns me most now is that when I did not remove the app from the device, even when I ran it with the PauseButton already completely removed from IB but still having an IBOutlet on my ViewController it would still show the 22px constraints like it was still there.
Only after adding it again and deleting the app from the device it gave up on these phantom constraints.
My guess is that because you've only set the aspect ratio, and not a height, width, trailing, or bottom constraint; its sizing the button to fit its content while still respecting the aspect ratio. Try changing the buttons title or image, or adding additional constraints to so see if it sizes more appropriately.
Related
When using iPhone app on a iPad, the keyboard extension can only know the screen size of an iPad, but actually I should know it's like running on an iPhone, and get a size like an iPhone.
My current code runs in a iPhone app on iPad look like this:
In viewDidAppear of UIInputViewController, I can actually get the frame of self.inputView, that is 320 in this case.
However, self.view and self.inputView's frame are both CGRecteZero in viewDidload or viewWillAppear, and that's actually why we should set keyboard height only after [super viewDidAppear].
The actual size of the keyboard view(self.view.frame) can be checked right after invoking [super viewDidLayoutSubviews] in viewDidLayoutSubviews of UIInputViewController. It is the 1st place to check the size and much faster than viewDidAppear.
Your Keyboard extension should be universal, if so then you can get device type (iPhone/iPad) by using below code.
let isPad = UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad
It may possible that you are using below method to get screen bounds
UIScreen.mainScreen().bounds.size
This method gives device's whole height, width in keyboard extension.
You can use nested condition by using above two condition to solve your problem.
I'm super confused about how the iPhone 4 and below apps are optimised for the iPhone 5. I'm a designer. How is optimisation done? Basically I have an app with loads of knobs and buttons and other interactive components. The way I see it, there are 3 options but what is the best way (or least, standard practice)?
1) Keep the layout the same for iPhone 5 but just add extra length on the background.
2) Scale the images/layout of components from iPhone 4 to iPhone 5 so everything is proportional.
3) Have completely separate images and different layouts for both. i.e for iPhone 5, I can move components to utilise the space more. The problem with this (and I'm not a developer) is that the interaction position of the components have moved so in effect, the iPhone 4 and 5 are separate apps?
To identify if it is a iphone 5 iphone, use this code in the file nameYourApp-prefix.pch:
//Macro iPhone5
#define IS_IPHONE_5 ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON )
find the nameYourApp-prefix.pch file in the supporting files, the code must be written between:
# ifdef __ OBJC__
//Code Macro iPhone5
# endif
and then just use the "if" to check that device, like this:
if (IS_IPHONE_5) {
//Code for 4 inch screen
}else{
//Code for 3.5 inch screen
}
putting the macro in the nameYourApp-prefix.pch, all classes see it.
With a UIKit application, none of your options are ideal. You simply use Springs and Struts or Auto Layout to have your user interface elements snap to where they should be.
The solution is the same when switching between portrait and landscape orientation. You define which user interface elements can grow and which ones can't, which edges to snap to, etc.
Run it on the simulator to test the layout if you don't have an iPhone 5 device.
For example, for a UITableViewController, the UITableView should grow vertically on an iPhone 5 so that you see more rows.
I almost always have at least one control that would be nice to grow given the room. If you don't, then you'll probably just have more empty space at the bottom of the view.
How iPhone 5 Optimization Works
When you tell iOS that your app is optimized for the iPhone 5 (through use of a cleverly named image file), iOS uses your existing Springs and Struts or Auto Layout (whatever you're using) to rearrange the interface.
The first choice is the easiest and works pretty well. You'd only need to worry about having specific assets for full screen images, and make sure that NavigationBar and TabBar (in case your app have one), remains on top and bottom respectively.
Scaling other images/components such as buttons, you'll change the general aspect ratio (which you probably wouldn't want), since iPhone 4&5 display sizes only differ in height.
For developing app option two is better. Although you dont have to change all the images for iPhone 5 some images will me same like TabBarItem. But you have to create background image different for iPhone4 and 5.
Read out this apple document to create user interface.
And you can differentiate iPhone 4 from iPhone 5 using this code
UIBtton * btn = [[UIButton alloc] init];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
if (screenBounds.size.height == 568)
{
btn.frame = CGRectMake(60,60, 205, 175);
//this is for iPhone5
}
else
{
btn.frame = CGRectMake(60,50, 199, 170);
//this is for iPhone4
}
Here my button place and length are different for iPhone 5 and iPhone 4
And set your control's hight and width according to iPhone Screen Size.
If you didn't, adjust your view layouts with proper auto resizing masks or look into Auto Layout if you only want to support iOS 6 going forward. You have to check height of [[UIScreen mainScreen] bounds].
Example:
CGRect screenBounds = [[UIScreen mainScreen] bounds];
if (screenBounds.size.height == 568) {
// code for 4-inch screen (iPhone 5)
} else {
// code for 3.5-inch screen (< iPhone 5)
}
Note that [UIImage imageNamed:#"yourImage.png"] will only load either "background.png" or "yourImage#2x.png", it will not load "yourImage-568h#2x.png" if it exists.
Check Auto-Rotation API as Well.
I have been looking everywhere on here and Google, but I still have this weird issue that I can't figure. I have an app pictures below, with 4 UIButtons that have a background image applied:
When I resize/run on the iPhone 5, the top button resizes in a very strange way:
How do I get all the UIButtons to resize the same way with the iPhone 5? Is it possible? Do I need to use a different method?
I really hope this is not a duplicate, but I am completely stumped. If I change the UIButtons around in other positions along the UIView, a different one will resize, or strange gap spacing will show up.... I don't understand. Thanks for any help!
EDIT: I am using iOS 6 on Xcode 4.5. I am testing on an iPhone 4S and an iPhone 5. I have AutoLayout checked. If I use Pin (just found it) I can get the UIButton to keep its height.
To clarify my question. I want the UIButtons to resize so they occupy the same percentage of the screen.... I want the proportions to be the same, instead of simply adding a bunch of empty space. I hope that make it more clear
One of the features you can use is called AutoLayout. Its provided with Xcode to make it easier adjusting things for the 4 inch screen. You can find a good example here: http://www.raywenderlich.com/20881/beginning-auto-layout-part-1-of-2
But as far as I know AutoLayout works only with iOS 6 which makes it "not so useful yet". I have been using a class called UIDevice, source of which can be found here: https://github.com/erica/uidevice-extension and check if the platform type is iPhone 5. I have it set up in a method which setups GUI when the view loads. Example of my implementation:
UIImage *backgroundImage;
if ([[UIDevice currentDevice] platformType] == UIDevice5iPhone)
{
//I have a 4 inch screen present
myButton.frame = CGRectMake(x, y, w, h); //Set the frame as you would like it to look on the iPhone 5
backgroundImage = [[UIImage alloc] initWithCGImage:[UIImage imageNamed:#"main_background-568h#2x"].CGImage scale:2.0 orientation:UIImageOrientationUp];
}
else
{
//I have a 3.5 inch screen present
myButton.frame = CGRectMake(x, y, w, h); //Set the frame as you would like it to look on the iPhone 3, 3gs, 4, 4s...
backgroundImage = [[UIImage imageNamed:#"main_background"] retain];
}
self.view.backgroundColor = [UIColor colorWithPatternImage:backgroundImage];
[backgroundImage release];
Hope it clears a bit.
you should firstly disable the use AutoLayout this option is under the "Show the file inspector"
I've created a custom button out of layers. I create a bunch of layers in init. Everything works as expected until I go to modify* them. It is only on the actual devices that the problem occurs, on the simulator the code works as expected.
I've tried forcing the layers to render by setting needsDisplay and needsLayout to YES.
-(void) setBackgroundColor:(UIColor *)backgroundColor
{
//return; // if I return here, the button gets initialized with it's default royal blue color. This works correctly on the simulator and device
[primaryBackground setColors: [NSArray arrayWithObject:(id)backgroundColor.CGColor]];
//return; //if I return here, primaryBackground will display a clear background on the device, but will display CGColor on the simulator.
CALayer* ll = [CALayer layer];
ll.frame=self.frame;
ll.cornerRadius=primaryBackground.cornerRadius;
[ll setBackgroundColor:backgroundColor.CGColor];
[primaryBackground addSublayer:ll];
//if I return here, primaryBackground will display the "expected" results on both platforms. IE: it dispalys the new layer, but not it's background color.
}
SIMULATOR
DEVICE
TESTS
I've tested on iOS 5.1 and 4.2 with the same results. Appears to work the same on simulators version 4.1, 4.2 and 5.1
I see at least 2 problems with your code:
You are adding a new layer every time the color is changed, without removing the old one. First, remove the previously created sublayer (or simply change its color without creating and adding a new one). Remember that the default opaque property of CALayer is set to NO, which means it is being blended with all the other sublayers.
Why adding another sublayer anyway? In your code, you cover primaryBackground completely. Can't setBackgroundColor: function just look like this:
- (void)setBackgroundColor:(UIColor *)backgroundColor {
[primaryBackground setBackgroundColor:backgroundColor.CGColor];
}
?
But if you really must have an additional layer, you should set ll's frame to the bounds of the superlayer (not frame). It should be ll.frame = self.bounds. You should also override the - (void)layoutSubivews method and set the frame of the background layer to the bounds of the root layer again.
Remember that, on iPhone Simulator, things are rendered by different subsystem and a lot faster than on real device. And some frame calculations may overlap, etc.
I have a viewController and I have
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
why I read w and h as 320 and 480 on its viewDidLoad for width and height, instead of 480 and 320, respectively, when iPhone is landscape? (see below)
- (void) viewDidLoad {
CGFloat w = self.view.bounds.size.width; // 320???
CGFloat h = self.view.bounds.size.height; // 480 ??
}
What am I missing?
thanks.
Be careful, it is possible you have run in to perhaps the most famous bug on the platform:
iPhone app in landscape mode, 2008 systems
This is a bug that catches a lot of people. Notice that on that question, one of the responses has 22 votes - that response is actually completely, totally, 100% wrong.
That shows some of the confusion on the issue.
Be sure to check out everything on there and all the links, eg:
"An important reminder of the ADDITIONAL well-known problem at hand here: if you are trying to swap between MORE THAN ONE view (all landscape), IT SIMPLY DOES NOT WORK. It is essential to remember this or you will waste days on the problem. It is literally NOT POSSIBLE. It is the biggest open, known, bug on the iOS platform."
Did you try to obtain the values in viewDidAppear? With viewDidLoad the view was not actually rendered and so it might be still in portrait.
I always have this problem...
I think that all views must render in portrait mode and then switch to landscape. Its a bitch, but the best way to manage this is to use UIViewAutoreizingMasks on all sub-views, that way they'll layout how you want them to.