With iOS 8 and Xcode 6, in storyboards we now have the screen size grid letting us select a size class. Where you can select layout formatting for the different screen sizes.
I have found this brilliantly helpful, as it allows me to set the base constraints and then unique ones for each screen size.
My question is, can you do this programmatically? I create my NSLayoutConstraint as normal but I need to be able to specify different constraints for different screen sizes.
iOS 8 introduces the active property on NSLayoutConstraint. It allows you to activate or deactivate a constraint. There are also methods to activate/deactivate multiple constraints.
+ (void)activateConstraints:(NSArray *)constraints
+ (void)deactivateConstraints:(NSArray *)constraints
Keep your constraints in arrays when creating them programmatically.
Create an array for each of the layouts you need.
Activate/Deactivate whatever set of constraints you need from within willTransitionToTraitCollection
To answer your question, you can set the size class programmatically, however, it's a bit of a pain. You must call "setOverrideTraitCollection" but from the parent view controller, not the one you actually wished to make a trait change to.
In my situation, I wanted to change the Master VC of a split view controller on iPad to look differently than the one on the iPhone, however, they are both set to Compact width / Regular height by default. So I subclassed the Master's nav controller and added code to set the Master's traits to Regular width when it's not an iPhone.
Swift code:
class MasterNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if (self.traitCollection.userInterfaceIdiom != .Phone) {
let newTraitCollection = UITraitCollection(horizontalSizeClass: .Regular)
self.setOverrideTraitCollection(newTraitCollection, forChildViewController: self.topViewController)
}
}
}
I hope this helps someone looking for a similar solution.
It's a bit confusing & hard to find in the documentation because a "size class" isn't actually a "Class" like NSObject. They're really defined in an enum/typedef called: UIUserInterfaceSizeClass
The way to get the horizontal & vertical size class for a view is with a UITraitCollection
Class/Type methods for UITraitCollection allow you to create one based on a particular display scale (e.g. retina or not), from an array of other trait collections, with a UI idiom (iPad/iPhone), or specific horizontal & vertical options (compact, regular), but to be honest I'm not sure yet how you'd use this...
This question discusses updating constraints when the traitCollection changes, using willTransitionToTraitCollection(newCollection: UITraitCollection!,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!)
You're right that both the UITraitCollection and its properties are readonly, but clearly you can create a new collection for some reason, and handle layout changes when the traitCollection changes.
This previous question is pretty similar & links to an Apple article about using Adaptive Layout. Also check the WWDC video "Building Adaptive Apps with UIKit."
Related
Since upgrading to XCode8 GM and ios10, all of my views created via Interface Builder are not being initialized correctly until much much later than expected. This means in viewDidLoad, cellForRowAtIndexPath, viewWillAppear, etc, the frame size is set to {1000,1000} for every view. At some point they seem to correct, but its far too late.
The first problem encountered is with common rounding of corners failing across the board:
view.layer.cornerRadius = view.frame.size.width/2
Further problems are showing for anything that relies on frame size to do calculations in the code.
cellForRowAtIndexPath
For cellForRowAtIndexPath, frame size fails on initial table display, but then works fine once you scroll it. willDisplayCell:forRowAtIndexPath does not have the correct frame size either.
I've hardcoded a few values but obviously this is very bad code practice, as well as quite numerous in my projects.
Is there a way or place to get correct frame sizes?
EDIT
I've discovered that using the height/width constraint instead of frame width height is more reliable. This may add the overhead of needing lot of new IBOutlets to link the height/width constraints on items though.
For now I've created a UIView category that lets me access a View's height/width constraints directly without the IBOutlets. For minimal use the small loop shouldn't be a big deal. Results not guaranteed for IB items without the width/height constraints created yet obviously. Probably returns 0 at best for the constant, or worse. Also, if you don't have a height/width constraint and your view is sized dynamically based on leading/trailing constraints, this won't work.
-viewDidLoad appears to have correct frame size, but will often result in a visual change to the UI if you do modifications here.
UIView+WidthHeightConstraints.h
#interface UIView (WidthHeightConstraints)
-(NSLayoutConstraint*)widthConstraint;
-(NSLayoutConstraint*)heightConstraint;
-(NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute;
#end
UIView+WidthHeightConstraints.m
#import "UIView+WidthHeightConstraints.h"
#implementation UIView (WidthHeightConstraints)
-(NSLayoutConstraint*)widthConstraint{
return [self constraintForAttribute:NSLayoutAttributeWidth];
}
-(NSLayoutConstraint*)heightConstraint {
return [self constraintForAttribute:NSLayoutAttributeHeight];
}
-(NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute {
NSLayoutConstraint *targetConstraint = nil;
for (NSLayoutConstraint *constraint in self.constraints) {
if (constraint.firstAttribute == attribute) {
targetConstraint = constraint;
break;
}
}
return targetConstraint;
}
#end
EDIT 2
The category above has proven only partially effective. Mainly because ios appears to auto add a couple extra height/width constraint duplicates, that are of type NSContentSizeLayoutConstraint, which are actually not the same size as the normal constraint. The NSContentSizeLayoutConstraint is also a private class so I can't do isKindOfClass to filter those out. I haven't found another way to effectively test for those yet. This is annoying.
The most common issues you describe are appearing in iOS 10 only and can be solved by adding this line (if necessary):
self.view.layoutIfNeeded()
just above the code, that is responsible for changing constraint, layer.cornerRadius etc.
OR
place your code related to frames / layers into viewDidLayoutSubviews() method:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.layer.cornerRadius = self.myView.frame.size.width/2
view.clipsToBounds = true
... etc
}
We created a radar (28342777 (marked as duplicate for 28221021 but Open)) for the similar problem and the reply that we got was as below:
"Thank you for reporting the issue. Could we get more information about the profile image view? In Xcode 8, a fully constraint, non-misplaced view no longer saves out a frame to minimize diffs and support automatically update frames in IB. At runtime, these views get decoded with a placeholder size of 1000x1000, but are resolved after first layout. Could the image be assigned before initial layout, and would assigning the image to the image view after first layout address this case? Please send a sample to help us further analyze. thanks!"
At present we have provided them the sample project. My observations:
The problem that we had used to happen for XIBs that are converted from Xcode 7.x to Xcode 8.x
If we intentionally break the constraint in XIB then viewDidLoad will get expected height and width and not 1000x1000.
For us it was a UIImageView on which we were apply some layering for making it circular and using masksToBounds. If we set masksToBounds = NO then we everything was working fine.
Though Apple claims that it is going to be a standard from Xcode 8 that views will be set to 1000x1000, the behavior doesn't seem to be consistent.
Hope this helps.
I encountered the same issue and try to solve it without luck by referring above suggestions.
Seems it should be a bug for Apple to solve. I finally find a solution by changing to save my XIB document back to Xcode 7.x format and my UI back to normal.
Until Apple releasing a fix, I don't want to spend my time on hacking it.
What about doing this:
- (NSLayoutConstraint*)widthConstraint{
return [self constraintForAttribute:NSLayoutAttributeWidth];
}
- (NSLayoutConstraint*)heightConstraint {
return [self constraintForAttribute:NSLayoutAttributeHeight];
}
- (NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute {
NSLayoutConstraint *targetConstraint = nil;
for (NSLayoutConstraint *constraint in self.constraints) {
//NSLog(#"constraint: %#", constraint);
if (![constraint isKindOfClass:NSClassFromString(#"NSContentSizeLayoutConstraint")]) {
if (constraint.firstAttribute == attribute) {
targetConstraint = constraint;
break;
}
}
}
return targetConstraint;
}
You should never rely on the timing of when a view is layed out. If that worked for you before, then out of pure luck. There are very little guarantees about this in UIKit. If you rely on something adopting to the size of your view, the right thing to do is override layoutSubviews in that view and adjust your stuff there.
Even after your view is fully rendered on screen, there are still so many conditions that could cause the size of the view to change. For example: Double height status bar, multitasking on iPad, device rotation, just to name a few. So it never is a good idea to do frame related layout changes at a particular point in time.
I was having the exact same problem. I had custom UITableViewCell subclasses and was using clipsToBounds = YES and self.iconView.layer.cornerRadius = self.iconView.frame.size.width/2 to give myself a circular image. Tried calling my cell configuration method from cellForRowAtIndexPath and willDisplayCell and neither worked.
Here is what works:
Move your layering code into the cell's -layoutSubviews method like this:
-(void)layoutSubviews {
[super layoutSubviews];
self.iconView.clipsToBounds = YES;
self.iconView.layer.cornerRadius = self.iconView.frame.size.width/2;
}
After this the images should load properly and your layering code should also work.
Only Update frame in your autolayout box .
I've created my view controller's view entirely in code by adding subviews and constraints (using the CocoaPod PureLayout). It actually looks/ functions exactly how I want it to, but xcode yells at me saying one of the constraints is wrong and it removes it. How can I tell which UIViews the bad constraints are attached to? I have a lot of subviews and I can't figure it out from context. When working with constraints in interface builder, you can name the UIView and that's what gets printed to the debug console- but I can't find a way to accomplish this through code.
Looking at the apple docs: https://developer.apple.com/library/ios/documentation/userexperience/conceptual/AutolayoutPG/ResolvingIssues/ResolvingIssues.html#//apple_ref/doc/uid/TP40010853-CH17-SW14
They say:
"It may be obvious which view has the problem; if it is not, you may find it helpful to use the NSView method _subtreeDescription to create a textual description of the view hierarchy.
Important: The _subtreeDescription method is not public API; it is, however, permissible to use for debugging purposes"
Is this what I'm looking for? How do I make use of a private API?
Starting with the iOS 8 SDK, there is now an identifier property on NSLayoutConstraint. Note that this property has existed privately (Apple-only) since iOS 7, but now that it is public in iOS 8, you are allowed to make use of it even when running on iOS 7.
Using this property, you can easily set a unique short description to your constraints to aid in debugging. For example, using PureLayout:
NSLayoutConstraint *constraint = [label autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:imageView];
constraint.identifier = #"Label Left Padding";
// ...or using PureLayout v2.0+:
[[label autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:imageView] autoIdentify:#"Label Left Padding"];
// PureLayout v2.0+ also supports a block-based version to set an identifier to many constraints at once:
[UIView autoSetIdentifer:#"Constraints to position image view" forConstraints:^{
// ...a bunch of PureLayout API calls here that create constraints...
}];
Then, you will see this identifier printed next to the constraint in the console if there is a constraint exception.
One other handy debug tool:
Apple has a very handy category on UIView (declared in UIView.h) named UIConstraintBasedLayoutDebugging that includes a method:
- (NSArray *)constraintsAffectingLayoutForAxis:(UILayoutConstraintAxis)axis;
You can call this method on any view, passing either Horizontal or Vertical axis (since constraints in each axis are independent), and get a list of all the constraints that affect the position and size along that axis. Note that Apple says this should be used for debugging only - never ship code that uses this API!
UIWindow also has a private instance method _autolayoutTrace to dump a string that shows the overall view hierarchy, including views that are ambiguous. Just use it from the console after setting a breakpoint after you see the constraint exception. You can also trap any autolayout exceptions using a symbolic breakpoint "UIViewAlertForUnsatisfiableConstraints".
Check out Facebook's Chisel too: https://github.com/facebook/chisel
Apologies if this is a silly question, but I've done some googling and searched SO and haven't found anyone asking this exact question.
I have been doing iOS development for some time now, but I am completely new to the Interface Builder. What I want to know is this: is there any way to just create ONE .xib file and then use it for both iPhone and iPad in a Universal application?
It seems silly to me to have to create them separately; why do twice the work laying something out more than once in Interface Builder when I could do it once (with minor adjustments for screen size) in code?
Please let me know if I'm missing/misunderstanding something here. Like I said, I'm a complete Interface Builder newbie :)
EDIT: I have submitted non-interface-builder games to the App Store in the past where the iPhone and iPad versions were identical, so I'm not concerned with making the game look/feel different on each device. I intend for them to look exactly the same, aside from some slight positioning changes due to the difference in aspect ratio.
If you know what the resulting view would look like, based on autoresizing, you can indeed use only one .xib. May come in handy if the view is just some sort of a shared component that autoresizes as you want it to. However, if you need the view to look way different on iPad than on iPhone, just use two .xibs. It’s possible then to load the appropriate one as needed, for example in instance initializer, like this controller’s -init:
- (id)init
{
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
{
self = [super initWithNibName:#"YourNibForPad" bundle:nil];
}
else
{
self = [super initWithNibName:#"YourNibForPhone" bundle:nil];
}
if (self) { /* initialize other ivars */ }
return self;
}
The main reason that XIBs are separate files is because Apple feel that UIs designed for iPhones/iPod touches and iPads should be tailored to each respectively. This is echoed in their their iOS App Programming Guide, which says the following:
For views, the main modification is to redesign your view layouts to support the larger screen. Simply scaling existing views may work but often does not yield the best results. Your new interface should make use of the available space and take advantage of new interface elements where appropriate. Doing so is more likely to result in an interface that feels more natural to the user—and not just an iPhone app on a larger screen.
Whilst it can take time to maintain two XIBs for what is effectively one UI, I feel it is more straightforward than using one XIB and then having to connect up most of your UI elements in order to move them around programmatically when that XIB loads. After all, with two XIBs at least you can see what each UI looks like, and then make visual changes easily.
As an aside, don't forget iOS 5's Storyboards (read about them here), which make managing a view/view controller hierarchy much simpler.
Try to name them
MyCell.xib and MyCell ~ ipad.xib
then:
[self.tableView registerNib: #"MyCell" forCellReuseIdentifier: #"MyUniqueIdentifier"];
If your using IB, you need to create 2 separate xib files for iPhone and iPad. You need a separate iPad xib to make your app comply with the Apple iPad UI guidelines.
Lately I've been running into some subtle layout issues in my iOS app. For example displaying a viewController from one part of the app causes the layout of some subviews to be altered (the z-axis ordering changes). Another subtle issue is the navigation bar flickering slightly.
What are some techniques for debugging these issues?
I'm especially interested in printing/logging properties of objects. For example I'd like to just dump/print/log all properties of the viewController referenced above to see exactly what changes. Then perhaps one can use symbolic breakpoints to pin-point the cause.
Check out DCIntrospect. It's a tool that can be very helpful for looking at view's info conveniently.
You can use KVO to observe frames changing, so you know what changes when, from and to what values. You can even use it to fix properties to some contant value. (See Prevent indentation of UITableViewCell (contentView) while editing)
You can use reflection to loop through all properties of an object. I don't know how such a broad approach would help you, but it is possible. (See Loop through all object properties at runtime)
Another technique to use is to subclass a UIView with override methods for re-positioning a view, or other aspects - then you can set breakpoints or log when the frame changes, or other attributes.
To use the UIView debugging class you can just change the type of a View in InterfaceBuilder to be your custom view type instead of UIView.
Use iOS App layout Debugging tool
revealapp.com
Just integrate revealapp SDK in your app and work as firebug
My App contains a news-section. I'm trying to create a View in my storyboard which will show an news item. A news contains of a title, a date, an image, and some text.
Content like this would be easy to display in HTML since it's content is floating, but as far as I understand iOS elements have a fixed size. I think a UILabel could be fine for title and date, UIImage for the image and a UITextView for the text. I think all elements should be inside a scroll view. The problem is, that the title for some news will fill one line but for other news multiple lines.
How can I handle a setup like this in a storyboard? A link to a guide, tutorial would be fine.
There are a lot of different ways you could approach this. Ultimately you have control of the size of any of these elements at runtime. From a UX point of view you'd probably want to keep at least some of the elements fixed in size (e.g., the image and the scrollview representing the total size of the news item). That would leave playing with the relative sizes of the title and the detail. If you want to show the entire title you can use the sizeWithFont method (or one of its variations) of the NSString class to calculate how much space to use for the title, and allocate space from the remaining amount to the detail as desired.
UPDATED: I might add that I intended this as a general strategy, not specifically aimed at the new iOS 5 Storyboard functionality.
UPDATED: Well, no tutorial but here are some general guidelines (at least about the way I might tackle it).
I'd set up a NewsItem class that encapsulates the elements that you describe here, similar to what I've laid out in the example image. How you expose NewsItem is something of a matter of taste; you might want to start with a UITableView as a container as it would make managing a NewsItem collection simpler. I'd subclass UITableViewCell as NewsItemTableViewCell, and generate a nominal layout for a NewsItem using Interface Builder. So in the example image, a news item would correspond to one table row.
The main part that you are asking about is identified by the red and blue frames in the image. Using the above approach, I would add logic in the NewsItemTableViewCell to calculate the extent of the title -- this assumes that the View (in this case, the NewsItemTableViewCell) has explicit knowledge of the Model (the underlying data for the news item) in the form of a reference (i.e, an instance of a NewsItem class
that contains the items you've described retrieved from a web service). If the width of the title is greater than the default width of the red frame, I'd adjust the height of the red frame (increasing it) at the same time decreasing the height of the blue frame. This kind of approach avoids having to include extra logic for differing cell heights within the table.
An alternative I often use is to have a Controller class mediate between the View and the Model. This can be useful when you have a requirement that your table contain different kinds of cells; it also makes tableview cell reuse much simpler. In this case, declare a base class. This might look something like:
#protocol GenericCellController
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath;
#optional
- (void) tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath;
#end
and then:
#interface NewsItemTableCellController : NSObject<GenericCellController> {
NewsItem* newsitem;
}
You then set up a collection of table cell controller objects. This has the advantage that when you implement tableView:cellForRowAtIndexPath: in your tableview controller code, you only have to delegate to the cell controller object at the appropriate row. What's more, you can refer to it using a superclass designation; e.g., in a hypothetical NewsItemTableViewController class you might have:
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*) path {
NSObject<GenericCellController>* cellController = [cellControllerArray objectAtIndexPath:indexPath.row];
UITableViewCell* cell = [cellController tableView:tableView cellForRowAtIndexPath:indexPath];
return cell;
}
This approach allows you to have any class derived from NSObject in the collection and polymorphically use delegation to have the derived class handle the rendering logic. The derived Controller class has its own implementation of tableView:cellForRowAtIndexPath: with a corresponding UITableViewCell-derived subclass as described above.
If you decide to go this way, there are numerous examples of the basics of tableviews and their controllers that you can use as a preface to implementing these ideas.