Debugging programmatic autolayout constraints - objective-c

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

Related

Set AutoLayout Size Class Programmatically?

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."

Don't Understand Apple's takeFloatValueFrom: Example

I'm no iOS guru but I know enough to build apps. I know and understand the patterns, UIKit, and Objective-C. I'm now learning Mac Development and this little bit of "Cocoa Bindings Programming Topics" has me stumped:
Take as an example a very simple application in which the values in a text field and a slider are kept synchronized. Consider first an implementation that does not use bindings. The text field and slider are connected directly to each other using target-action, where each is the other’s target and the action is takeFloatValueFrom: as shown in Figure 2. (If you do not understand this, you should read Getting Started With Cocoa.)
This example illustrates the dynamism of the Cocoa environment—the values of two user interface objects are kept synchronized without writing any code, even without compiling.
(Emphasis mine)
Huh? Wouldn't you need to create outlets? And an IBAction that goes something like
- (IBAction)takeFloatValueFrom:(id)sender {
self.slider.floatValue = [sender floatValue];
self.textField.floatValue = [sender floatValue];
}
Is this something Mac-specific? How do you actually hook up two controls with target-action in a XIB without writing any code and have their values locked?
When you're setting up an interface in Interface Builder, you can specify that it sends a message to another object whenever it changes in some way. What this example is showing is that you can hook these two objects up such that whenever the slider changes, it sends the message takeFloatValueFrom: to the text field, and vice-versa.
takeFloatValueFrom: is a method defined on NSControl, and both a text field and a slider are subclasses of NSControl.

Using `valueForKey` to access view in UIBarButtonItem, private API violation?

Since UIBarButtonItem doesn't subclass UIView, it's impossible to get at the normal characteristics like its frame.
One way to do this is [barButtonItem valueForKey:#"view"]
This works perfectly, and allows you to add a GestureRecognizer (for instance) to the underlying UIView.
However, is this a private UIKit API violation?
This is not private in terms of immediate rejection upon validation, but it's private enough to be considered fragile (that is, new iOS version can break your existing app in the app store that's using the code).
I can say, that a similar code (fetching backgroundView ivar of UIToolbar via KVC) has passed app store validation and is being used in production.
In case of possible bad things, you must wrap the method in #try { ... } #catch, so that you intercept KVC possibly failing in newer iOS release.
Five Pieces of Evidence for "It's Not Private"
It's a property that you can get to in other ways. Try this and one of those views is, in fact, the _view ivar of the UIBarButtonItem in question. This indicates that access to this UIView is not prohibited itself, though the KVO way in might be questionable (but I doubt it).
NSArray *array = self.toolBar.subviews;
for (UIView *view in array) {
view.backgroundColor = UIColor.greenColor;
}
They actually trigger the KVO for this property. ivars do not have to trigger the KVO API, right?
#Farcaller mentions a similar case which is for sale in the App Store. Since he/she answered within the first 20 minutes of the question being up there, it's reasonable (but not safe!) to assume that there might be thousands of apps in the App Store that do this.
This UIView gets subbed out each time the button is pressed, so you cannot just, for example, set a gesture recognizer on it and be done. You can, however, keep setting the same gesture recognizer every time the view gets replaced. To me, this is actually more evidence that it's not a private API thing, but rather you have to be very careful when using it (and use KVO to make sure you have the latest one).
My app is for sale in the App Store and does this.

Debugging subtle iOS view layout issues

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

UIImage for UIActionSheet button?

I know that UIActionSheets don't offer that much customization but what I am asking, is that instead of the grayish/white buttons, can I use a green button (my own UIImage)? I can supply my own image with the text already on there that I want; so using a normal UIActionSheet, can I supply my own image on one of the buttons? If so, how should I go upon doing that?
Thanks,
O.Z
#huesforalice is right - the cleanest way would be to create your own replacement of UIActionSheet. Basically you have 3 options:
A real replacement: You create a UIActionSheet-subclass to be protocol-compatible to ´UIActionSheetDelegate´. This would allow you to use it exactly as a UIActionSheet — but it might be a costly process to figure out when and why a UIActionSheet will call the delegates method implementation.
Even go a bit further and also extend the protocol. This will give you more possibilities, how to use it (i.e. allow picker to be used via new protocol methods), but will be even harder.
The most easiest way will be to create a very own implementation, that doesn't rely on UIActionSheet nor it's protocol — but it won't replace real UIActionSheet, in the meaning that you cannot drop it into your project and expect it to work. But you will have the highest degree on freedom.
I would recommend 3. I found a project, that is working like that. But be warned: It shows you how to do it in general, but has some poor underlying design-decisions:
It uses a method
- (void) addButtonWithTitle: (NSString*) buttonTitle buttonId: (NSInteger) buttonId textColor: (UIColor*) textColor textShadowColor: (UIColor*) textShadowColor backgroundColor: (UIColor*) buttonBackgroundColor increasedSpacing: (BOOL) spacing
Instead — IMHO — it should be
- (void) addButton: (UIButton*) button;
So you can add buttons with different designs more flexible, and don't depend for a section id, what is totally unnecessary, as the object has its own identity as an object already.
The method [actionSheet showWithAnimation:YES]; should be called
showor showAnimated: as …withAnimation: usually takes a block to perform a custom animation.
This ist not possible using UIActionSheet and the documented methods. You could write your own actionsheet, which you then probably would add to the main window and animate to slide up. Possibly there is a way to do what you want by analyzing the actionsheets private view hierarchy and adding custom buttons, but you have to keep in mind, that private view hierarchies may change from one iOS Version to another, so your app might break or might even get rejected from apple.