Custom control with and without autolayout - objective-c

I will be creating a custom control (based on UIView).
Is there a way it could work with UIViewControllers using auto layout as well as UIViewControllers that do not?

The control should work in both cases. The difference is that in one case you will use constraints to layout it and in the other one, setting the frame programmatically.
If your control contains other subviews, it is important how you configure these. For example, if you do the setup of the subviews in initWithFrame: and you use the given frame to layout them (meaning static frames, instead of constraints), in the auto layout viewControllers, the frames will no get updated.
However, layouting the subviews with constraints will have no impact on the non-autolayout viewControllers. The subviews will have the layout according to your constraints, in the given static frame of the custom control.
It is a general explanation since I don't know that much about your particular controller. Hope this makes sense and you can find helpful.

Related

Layer hosting NSView within NSOutlineView

I am trying to create a custom NSView that hosts a CALayer hierarchy to perform efficient display. This NSView is then embedded within a NSTableCellView that is displayed by a View-Based NSOutlineView.
The problem is that whenever I expand or collapse an item, all rows are being moved, but the layer's content remains displayed at the position it was before changing the outline.
Scrolling the NSOutlineView seems to refresh the layers and they resync with their rows at that point.
I have debugged this behavior using Instruments and it seems that the scrolling provokes a layout operation which updates the layers with a setPosition: call that should have occured when expanding or collapsing items.
Here is some sample code for a simple layer hosting NSView subclass.
#interface TestView : NSView
#end
#implementation TestView
- (instancetype)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
CAShapeLayer* layer = [CAShapeLayer layer];
layer.bounds = self.bounds;
layer.position = CGPointMake(NSMidX(self.bounds), NSMidY(self.bounds));
layer.path = [NSBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
layer.fillColor = [NSColor redColor].CGColor;
layer.delegate = self;
self.layer = layer;
self.wantsLayer = YES;
return self;
}
#end
I have tried a lot of potential solutions to this problem but I couldn't find any interesting method that gets called on the NSView instance that could be overriden to call [self.layer setNeedsDisplay] or [self.layer setNeedsLayout]. I also tried various setters on the CALayer itself such as :
layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
layer.needsDisplayOnBoundsChange = YES;
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
Can anyone help me figure out how to make this layer display properly inside a NSOutlineView?
I ended up answering my question. The problem wasn't in the way my TestView was implemented. I simply missed one of the steps for enabling CoreAnimation support within the application. The relevant reference is within the Core Animation Programming Guide.
Basically, in iOS Core Animation and layer-backing is always enabled by default. On OS X, it has to be enabled this way :
Link against the QuartzCore framework
Enable layer support for one or more of your NSView objects by doing one of the following
In your nib files, use the View Effects inspector to enable layer support for your views. The inspector displays checkboxes for the selected view and its subviews. It is recommended that you enable layer support in the content view of your window whenever possible
For views you create programmatically, call the view’s setWantsLayer: method and pass a value of YES to indicate that the view should use layers.
Once I enable layer support on any of the NSOutlineView's parents, the various glitches are solved.
It is difficult to read the NSOutlineView reference documents and find the information about cell reuse that is likely giving you fits here.
You may have looked at outlineViewItemDidCollapse: but it's kind of a useless for our issue, because it doesn't have a pointer to an NSView, and that's because it's older than view-based outline views.
Perhaps the one helpful mention, buried within the NSOutlineViewDelegate protocol, down in the section on view-based NSOutlineView methods, there is a single mention within outlineView:didRemoveRowView:forRow: that:
The removed rowView may be reused by the table, so any additionally inserted views should be removed at this point.
In other words, when you call the outline view's makeViewWithIdentifier:owner:, for a cellView or rowView with a particular ID you often get a recycled view. Especially often because of collapse. Incidentally, that method is from the NSTableView superclass, and in that reference, there's also this comment:
This method may also return a reused view with the same identifier that is no longer available on screen. If a view with the specified identifier can’t be instantiated from the nib file or found in the reuse queue, this method returns nil.
So you have the option of altering the view hierarchy or niling properties in didRemoveRowView:forRow. However, buried within a third cocoa reference, that for NSView, there is within the commentary on prepareForReuse, this comment:
This method offers a way to reset a view to some initial state so that it can be reused. For example, the NSTableView class uses it to prepare views for reuse and thereby avoid the expense of creating new views as they scroll into view. If you implement a view-reuse system in your own code, you can call this method from your own code prior to reusing them.
So, TL;DR, you need to implement prepareForReuse.
The pertinent references are (mostly) the superclasses of both NSOutlineView and NSTableCellView.
And, FWIW, there was a similar question here, where the questioner seems to indicate things are even worse than I think, in that NSOutlineView is more creative behind the scenes than NSTableView.
In my own work with outline views and embedded NSTextViews, I've seen wildly terrible rendering hiccups relating to expand/collapse/scroll that I seem to have managed in just the NSOutlineViewDelegate methods. On iOS they did everyone the favor of renaming makeViewWithIdentifier to the more explicit dequeueReusableCellViewWithIdentifier.
You shouldn't have to enable layer backing for any of the ancestor views (like the outline view).
In my experience, the layer immediately assigned to a view (as opposed to sublayers) doesn't need its bounds, position, or autoresizing mask to be set. It is automatically made to track the bounds of the view. In fact, I would avoid setting those properties, just in case that breaks the automatic synchronization with the view's bounds rect.
So, the question is: how are you arranging for the view to move or resize with its superview? Are you using auto layout? If so, did you turn off its translatesAutoresizingMaskIntoConstraints? If yes to both, what constraints are you setting on the view? If no to either, how did you position the view within its superview? What frame did you set? Also, is the superview configured to autoresize its subviews (probably yes, since that's the default)? What is your view's autoresizingMask?
You could also override -setFrameOrigin: and -setFrameSize: in your custom view class and call through to super. Also, add logging to show when that's happening and what the new frame rect is. Is your view being moved as you expect when you expand or collapse rows?

Hook on UIView's frame change from third party library

I am building a 3rd party library that I plan to integrate on different projects, so I need this to be as modular and have a non-destructive nature as possible. I need to "hook on" frame changes of UIViews. I am building a category and from that category method, I need to hook on that view's (it can be any regular UIView or subclass) frame change, and perform my additional logic. I've looked at KVO but as seen here https://stackoverflow.com/a/19701380/811405 it's not a safe approach.
How can I hook on frame change of a UIView? I can override setFrame: in my category, but I know that it is the single worst thing an Objective-C programmer can make: overriding a default method in a category. How do I achieve this?
UPDATE: I'm currently working on a really ugly (but working) solution:
I've created an invisible (empty drawRect:) UIView subclass.
I'm instantiating and adding it to the view of which I want to be notified of frame changes.
I'm setting that view's (the "superview") autoresizesSubviews to YES.
I'm setting my "invisible" view's autoresizing mask to both flexible width and height.
I'm overriding my "invisible" view's setFrame: to notify the appropriate object via delegation.
Because the superview will set frame of all subviews with flexible autoresizing mask when autoresizesSubviews is YES, this works, but it involves adding a hidden view inside a view, which is a bit hacky and may create problems in some scenarios (though most are corner cases). I'm still searching for a more suitable/less hacky solution.

Handling NSLayoutConstraints from code properly

I started to use AutoLayout in my latest project, and I am stuck at some point. I use a content view controller, which adds other view controllers as sub view controllers, and their views as subviews aswell. Everything is fine, except that my sub controllers' view have wrong view sizes.
One of my sub view controllers sets up something like a slider, which has a knob. The initial position of this know depends from the actual size of this view, which is not fix, because I reuse it in many forms. Everything is set up with AutoLayout. Of course, because the value of this slider is coming from another part of code, the position of the know can be described only something like this:
self.knobVerticalPositionConstraint.constant = topPos + height * percentage;
where height is depending on the size of the slider view itself.
The problem is, that when the whole thing gets displayed, parts of my UI have wrong sizes. I even have a wrong view size in the sub controller's -viewDidAppear.
I think this is a problem of how I wrote my code. Maybe not everything is in it's place. I have read this article, which promises a clean and elegant way to solve this type of issues, but it is never revealed:
http://doing-it-wrong.mikeweller.com/2013/06/ios-app-architecture-and-tdd-1.html
I have read the whole Internet, but I didn't found anything like this. It is full of stuff for beginners and first step guides about "How to use AutoLayout", but I never found any advanced tips.
So my specific question is: do you have any best practices, how to assemble a View Controller–View pair which uses AutoLayout from code? Where do you set up your constraints? Where do you update constraint constants like mine, which depends on the calculated values of the constraint equalation system? What code goes to the View and what to the controller? I'd be glad if you could share me some tips to solve this issue.
I have recently completed the migration of my first (and so far only) iOS project to Auto Layout. The stuff below is roughly what I have taken away from my efforts. It's not much, but since I wanted to write it down for myself anyway, here you go, maybe it helps.
Static constraints that never change go into a view controller's loadView override, or in a view's initializer (or some sub-method thereof)
Dynamic constraints which depend on other values calculated by the layout system go into a view controller's viewDidLayoutSubviews override, or in a view's layoutSubviews override (in the latter case it makes sense to call [super layoutSubviews] first to get updated values that the dynamic constraint can be based on).
After a change to a dynamic constraint, it may be necessary to tell the layout system that something changed. For instance, in a view's layoutSubviews override I call [super layoutSubviews] a second time after the constraint change so that the update is effective immediately
A view controller can also override updateViewConstraints, and a view can override updateConstraints, but I have not used these so far.
There are many other events that might trigger an update of dynamic constraints (e.g. keyboard events), but this is beyond the scope of best practices.
Some general guidelines:
Constraints are installed by the view controller or view who sets up the view hierarchy and therefore knows about and is responsible for the layout of the view hierarchy. Although this rule is very basic, it sometimes pays to keep it in mind.
The translatesAutoresizingMaskIntoConstraints property of a UIView is set by that view controller which installs constraints for that UIView
A view controller therefore does not set the translatesAutoresizingMaskIntoConstraints property of its own root view. Instead, this is the responsibility of the parent view controller.
View controller properties related to bar handling in iOS 7:
edgesForExtendedLayout, usually set to UIRectEdgeNone
topLayoutGuide and bottomLayoutGuide
make all bars opaque (by default all bars are translucent in iOS 7), which would then allow the property extendedLayoutIncludesOpaqueBars to kick in
And some traps:
Don't install constraints in UIWindow. When I tried this I had trouble when dismissing a VC after it was presented modally.
The view controller properties topLayoutGuide and bottomLayoutGuide should only be used inside a view controller's override of viewDidLayoutSubviews, and after they are used to install constraints one must invoke [self.view layoutSubviews]. This advice is actually from Apple's documentation of those two properties. Not following the advice may lead to weird layout issues (it happened to me).
If a view controller's view hierarchy contains one or more scroll views (e.g. table views), and there are unexpected layout results, then the view controller property automaticallyAdjustsScrollViewInsets is probably playing a role. Setting it to NO might help.
My personal favourite ressource, by the way, for understanding Auto Layout was and still is this section from the book "Programming iOS 6".

Strategy for measuring performance of an iOS app

I have a simple case of an iOS application I would like to measure: a UITableView built with custom extended cells. Each cell is a composition of a main views and some subviews. Based on some criteria I am hiding or showing subviews in table cell.
Supposing I have to decide if adding subviews when needed, or built xib with all the outlets and hide/show when needed.
What could be the best approach for deciding between those two ?
Supposing also you didn't read Apple guidelines about composition of cell, what concrete steps you would do either via code (by putting NSLog statement for example), or via Xcode instruments (which one to choose etc...) to confirm the choice.
This is quite a new matter for me, so please be as much specific as you can.
This http://blog.giorgiocalderolla.com/2011/04/16/customizing-uitableviewcells-a-better-way/ is a great resource.
Having many subviews with transparent backgrounds degrades scrolling performance.
Dynamically adding subViews could get difficult to track, and if you don't do it "right" then you'll end up with cells with too many "extra" subviews, via the cell reuse queueing mechanism.
You'll have to add the subViews in the init method of the cell itself and not in the cellForRow method of your data source. (unless you're using some way to track the creation/availability of any given subview, like viewWithTag etc.)

What's the best way to programmatically add and remove views and their controllers?

I'd love to see a detailed explanation on how to manage views programmatically. I'll provide an overview of how I'm doing it now and would like either comments on how my approach sucks or just an overview of how to do it cleanly and properly.
Basically, in my app's main view controller's loadView method, I first create a root view and set self.view to it. When I want to attach a controller, say, the first one that displays, I call this method:
-(void) attachViewForController:(UIViewController*)controller
{
[self.mRootView addSubview:controller.view];
[controller viewWillAppear:NO];
}
Notice that I explicitly call viewWillAppear (I believe it wasn't automatically calling it), where I manually perform any animations to bring the view in (sliding in, fading in, etc). Is there anything wrong or strange with this approach here?
Now, when I want to leave this view and switch to another, I call a method to setup the switch:
-(void) setControllerSwitch:(UIViewController*)outgoingController
incomingController:(UIViewController*)incomingController
delay:(float)delay;
{
self.mOutgoingController = outgoingController;
self.mIncomingController = incomingController;
self.mSwitchControllerTimer = [NSTimer scheduledTimerWithTimeInterval:delay target:self selector:#selector(switchControllerCallback) userInfo:nil repeats:NO];
}
At this moment in time, I've begun the exit animations for the outgoing view, and this method records the controllers and schedules a method that will perform the actual switch at the moment the outgoing view is done animating. Like this:
-(void) switchControllerCallback
{
self.mSwitchControllerTimer = nil;
// remove outgoing view
[mOutgoingController.view removeFromSuperview];
// add incoming view
[self attachViewForController:mIncomingController];
}
Is this a decent way to manage things? A few points:
I know I could probably instead setup a callback to trigger when the outgoing controllers animations end, but chose to just do it via an explicit delay param to leave room to allow me to cross fade views. However, I think calling setControllerSwitch early may in fact not allow crossfading because it would junk the old controller early and make it chop off its animation.
As mentioned earlier, I'm curious to know if explicitly calling viewWillAppear is a no no and there is a more appropriate way to manage view flow.
Copied from Apple developer documentation but it help me lot to understand about views and maneging multiple views.
Tips for Using Views Effectively
Custom views are useful for situations where you need to draw something the standard system views do not provide, but it is your responsibility to ensure that the performance of your views is good enough. UIKit does everything it can to optimize view-related behaviors and help you achieve good performance in your custom views. However, you can help UIKit in this aspect by considering the following tips.
Views Do Not Always Have a Corresponding View Controller
There is rarely a one-to-one relationship between individual views and view controllers in your application. The job of a view controller is to manage a view hierarchy, which often consists of more than one view used to implement some self-contained feature. For iPhone applications, each view hierarchy typically fills the entire screen, although for iPad applications a view hierarchy may fill only part of the screen.
As you design your application’s user interface, it is important to consider the role that view controllers will play. View controllers provide a lot of important behaviors, such as coordinating the presentation of views on the screen, coordinating the removal of those views from the screen, releasing memory in response to low-memory warnings, and rotating views in response to interface orientation changes. Circumventing these behaviors could cause your application to behave incorrectly or in unexpected ways.
For more information view controllers and their role in applications, see View Controller Programming Guide for iOS.
Minimize Custom Drawing
Although custom drawing is necessary at times, it is also something you should avoid whenever possible. The only time you should truly do any custom drawing is when the existing system view classes do not provide the appearance or capabilities that you need. Any time your content can be assembled with a combination of existing views, your best bet is to combine those view objects into a custom view hierarchy.
Take Advantage of Content Modes
Content modes minimize the amount of time spent redrawing your views. By default, views use the UIViewContentModeScaleToFill content mode, which scales the view’s existing contents to fit the view’s frame rectangle. You can change this mode as needed to adjust your content differently, but you should avoid using the UIViewContentModeRedraw content mode if you can. Regardless of which content mode is in effect, you can always force your view to redraw its contents by calling setNeedsDisplay or setNeedsDisplayInRect:.
Declare Views as Opaque Whenever Possible
UIKit uses the opaque property of each view to determine whether the view can optimize compositing operations. Setting the value of this property to YES for a custom view tells UIKit that it does not need to render any content behind your view. Less rendering can lead to increased performance for your drawing code and is generally encouraged. Of course, if you set the opaque property to YES, your view must fills its bounds rectangle completely with fully opaque content.
Adjust Your View’s Drawing Behavior When Scrolling
Scrolling can incur numerous view updates in a short amount of time. If your view’s drawing code is not tuned appropriately, scrolling performance for your view could be sluggish. Rather than trying to ensure that your view’s content is pristine at all times, consider changing your view’s behavior when a scrolling operation begins. For example, you can reduce the quality of your rendered content temporarily or change the content mode while a scroll is in progress. When scrolling stops, you can then return your view to its previous state and update the contents as needed.
Do Not Customize Controls by Embedding Subviews
Although it is technically possible to add subviews to the standard system controls—objects that inherit from UIControl—you should never customize them in this way. Controls that support customizations do so through explicit and well-documented interfaces in the control class itself. For example, the UIButton class contains methods for setting the title and background images for the button. Using the defined customization points means that your code will always work correctly. Circumventing these methods, by embedding a custom image view or label inside the button, might cause your application to behave incorrectly now or at some point in the future if the button’s implementation changes.