[CALayerDelegate displayLayer:] is described here.
[NSView updateLayer] is described here.
How do they differ? When would I use one over the other?
-[NSView updateLayer:]
The purpose of this method is to be overridden by an NSView subclass so that you can customize the backing layer. By default, -[NSView drawRect:] will be used and you're not supposed to manipulate the backing layer directly (like you might with UIView on iOS). If you want to customize the backing layer, you're supposed to use -updateLayer and perform your customizations in this method. To opt into using updateLayer rather than drawRect:, you override -[NSView wantsUpdateLayer] and return YES. Now you can change any property on the backing layer inside of -updateLayer. To notify the view that a change needs to occur you would use the needsDisplay property and set it to YES, which will trigger -updateLayer when it does a render pass. It's also good to know about the layerContentsRedrawPolicy property that controls when a redraw is triggered.
Example usage:
#implementation MyView
- (BOOL)wantsUpdateLayer {
return YES;
}
- (void)updateLayer {
// Perform different customizations based on view/control state.
self.layer.backgroundColor = NSColor.redColor.CGColor;
self.layer.contents = <some image>;
}
#end
// Example of notifying MyView instance that it needs to update itself
myView.needsDisplay = YES;
Shameless plug of a video I did on this topic somewhat recently.
-[CALayerDelegate displayLayer:]
This call is specific to CALayer. If you aren't dealing with NSView, then this is how you can be notified of CALayer needing a change as a result of calling -[CALayer setNeedsDisplay].
In short, if you're dealing with NSView and want to have full control over the backing layer, you must override -wantsUpdateLayer to return YES and implement -updateLayer. If you're directly using CALayers (no NSView involved), then the delegate can be useful here.
Here are my own findings.
If you override -[NSView makeBackingLayer], you must use -[CALayerDelegate displayLayer:] instead of -[NSView updateLayer].
CALayer vs. NSViewBackingLayer
If you don't override makeBackingLayer and you set wantsLayer to YES, the default layer class is called _NSViewBackingLayer.
It behaves differently than CALayer in the following ways:
takes up noticeably less memory.
calls setNeedsDisplay:YES when its transform property is set.
maybe other ways too...?
Related
I have this layer hosted view, which is initialised like so:
// Init layers
self.wantsLayer = YES;
_hostedLayer = [CALayer layer];
_hostedLayer.delegate = self;
self.layer = _hostedLayer;
Weirdly, the delegate method updateLayer is not called.
When I comment out the last 3 lines, it does get called.
What's wrong here?
Hint: Yes I have overridden wantsUpdateLayer and return YES.
When a view asks for a layer it gets a special, private subclass of CALayer by default, which has extra capabilities. Although I haven’t done this since 10.7, in those days it was an all-or-nothing proposition—you either used the default (private) layer the view got, and got to draw using AppKit conventions, OR you made your own CALayer and drawing was all handled by the CALayer itself or by the delegate methods:
- (void)displayLayer:(CALayer *)layer;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
So, I’d guess in your case these latter two methods would be called on your view, but not the view-specific -updateLayer.
Okay so i decided to move my code for my UITableView delegates into another class. a subclass if you will, A subclass so that it would make it easier to access all the elements my Cellforrowwithindexpath function does within said subclass.
But now there is a slight issue...
It works fine, as far as the UItableView is concerned, But then when i tried to use the navigation controller to push a view on top, it did not work, i then discovered that self...Within my main class was actually an instance of my subclass...What? so self is not actually equal to self...
Can anyone give me any insight as to what i am doing so colossally wrong here?
EDIT: So i changed it to instead be a subclass to a delegate and it works fine, just in case anyone else runs into This issue, But i am still confused as to why it was happening in the first place...
Code:
#interface OpenGameList : MainMenuViewContoller <UITableViewDelegate,UITableViewDataSource>
{
}
#end
In my MainMenuViewController's viewDidLoad function
_openGameList = [[OpenGameList alloc] init];
_openGameList.delegate = self;
friendsTable.delegate = _openGameList;
friendsTable.dataSource = _openGameList;
And than after that it seems that any use of self in MainMenuViewController is equal to OpenGameList hence why using [[fromView navigationController] pushViewController:toView animated:NO]; does not work
self always points to the object that was actually instantiated – the most derived class.
When you have an instance of a subclass, and you send a message to self, the subclass's implementation will always be invoked if there is one. It doesn't matter whether or not you're in the superclass's implementation file or the subclass's implementation file.
This is an essential for polymorphism: it's what allows subclasses to override the behavior of a parent class. Take -[UIView drawRect:] for example. To invoke the drawing code for subclasses, when code in UIView invokes [self drawRect:] it's the subclass's drawing implementation which needs to be called.
It might help to remember that superclasses and subclasses aren't parent and child objects, but less and more specific types which apply to the same object. A UITableView is also a UIScrollView, UIView, and NSObject, but when you make one, there is one object which is all of those things, and self always refers to that one.
I am fiddling with the new UICollectionView and the UICollectionViewLayout classes. I have created a custom layout, subclassing UICollectionViewFlowLayout.
My cell sizes are changing dynamically and I set the item sizes using the delegate method below
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"SETTING SIZE FOR ITEM AT INDEX %d", indexPath.row);
return CGSizeMake(80, 80);
}
Now, under the prepareLayout method of my custom UICollectionViewFlowLayout class, I need to access these size variables so that I can make calculations how to place them and cache them for layoutAttributesForItemAtIndexPath.
However, I can't seem to find any property under UICollectionView or UICollectionViewFlowLayout to reach the custom item sizes I set in the delegate method.
Found it myself.
Implement the custom class like without omitting UICollectionViewDelegateFlowLayout
#interface SECollectionViewCustomLayout : UICollectionViewFlowLayout
<UICollectionViewDelegateFlowLayout>
and then you can call
CGSize size = [self collectionView:self.collectionView
layout:self
sizeForItemAtIndexPath:indexPath];
Looking at the various UICollectionView... header files, and watching the WWDC 2012 Session 219 - Advanced Collection Views and Building Custom Layouts video (from about 6:50 onwards), it seems the extensible delegate pattern takes advantage of dynamic typing to ensure the layout can properly access its extended delegate methods.
In short...
If you define a custom layout with its own delegate, define that delegate protocol in the layout's header file.
Your delegate object (typically the UI(Collection)ViewController that manages the collection view) should declare itself to support this custom protocol.
In the case that your layout is just a UICollectionViewFlowLayout or subclass thereof, this just means declaring conformance to UICollectionViewDelegateFlowLayout.
Feel free to do this in your class extension in the .m file if you'd rather not #import the layout header into the delegate's interface.
To access the delegate methods from the layout, call through to the collection view's delegate.
Use the layout's collectionView property, and cast the delegate to an object conforming to the required protocol to convince the compiler.
Don't forget to check that the delegate respondsToSelector: as usual prior to calling optional delegate methods. In fact, if you like, there's no harm in doing this for all methods, as the typecasting means there is no runtime guarantee the delegate will even implement the required methods.
In code...
So if you implement a custom layout that requires a delegate for some of its information, your header might look something like this:
#protocol CollectionViewDelegateCustomLayout <UICollectionViewDelegate>
- (BOOL)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath;
#end
#interface CustomLayout : UICollectionViewLayout
// ...
#end
Your delegate declares conformance (I've done so in the implementation file here):
#import "CustomLayout.h"
#interface MyCollectionViewController () <CollectionViewDelegateCustomLayout>
#end
#implementation
// ...
- (BOOL)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath
{
return [self canDoSomethingMindblowing];
}
// ...
#end
And in your layout's implementation, you access the method like this:
BOOL blowMind;
if ([self.collectionView.delegate respondsToSelector:#selecor(collectionView:layout:shouldDoSomethingMindblowingAtIndexPath:)]) {
blowMind = [(id<CollectionViewDelegateCustomLayout>)self.collectionView.delegate collectionView:self.collectionView
layout:self
shouldDoSomethingMindblowingAtIndexPath:indexPath];
} else {
// Perhaps the layout also has a property for this, if the delegate
// doesn't support dynamic layout properties...?
// blowMind = self.blowMind;
}
Note that it's safe to typecast here, as we're checking the delegate responds to that method beforehand anyway.
The evidence...
It's only speculation, but I suspect it is how Apple manages the UICollectionViewDelegateFlowLayout protocol.
There is no delegate property on the flow layout, so calls must go via the collection view's delegate.
UICollectionViewController does not publicly conform to extended flow layout delegate (and I doubt it does so in another private header).
UICollectionView's delegate property only declares conformance to the 'base' UICollectionViewDelegate protocol. Again, I doubt there is a private subclass/category of UICollectionView in use by the flow layout to prevent the need for typecasting. To add further weight to this point, Apple discourages subclassing UICollectionView at all in the docs (Collection View Programming Guide for iOS: Creating Custom Layouts):
Avoid subclassing UICollectionView. The collection view has little or no appearance of its own. Instead, it pulls all of its views from your data source object and all of the layout-related information from the layout object.
So there we go. Not complicated, but worth knowing how to do it paradigm-friendly way.
There is a swift version:
self.collectionView(self.collectionView, layout: self.collectionView.collectionViewLayout, sizeForItemAtIndexPath: indexPath)
Check out UICollectionView-FlowLayout on GitHub. Same idea, this just makes accessing the extended delegate methods of flowLayout a little cleaner.
For the later readers, IOS 7 has UICollectionViewFlowLayout which has defined it.
In my case everything about layout, cell layout etc. is being defined inside nib for UIViewController and separate nib for UICollectionViewCell. MyCollectionViewCell contains UIImageView with autolayout to cell with padding/margins but square-shaped.
I need round icons instead squared but don't want to take care which nib I use for iPhone or for iPad (I have separate nibs for devices and for orientation as well).
I don't want to implement #selector(collectionView:layout:sizeForItemAtIndexPath:) into my view controller.
So, inside collectionView:cellForItemAtIndexPath:
I can just use
CGSize size = cell.imageView.bounds.size;
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = size.height/2.0;
Because collectionView:layout:sizeForItemAtIndexPath: call before collectionView:cellForItemAtIndexPath: and layout done.
You can check round avatars on the bottom
What is the proper way of setting up a separate delegate class for MapKit?
I have MapView class subclassing MKMapView and bare MapDelegate class conforming MKMapViewDelegate protocol having only one initializer method.
Here is the extract from MapView initialization method I use:
# MapView.m ...
#implementation MapView
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// [self setShowsUserLocation:YES];
[self setDelegate:[[MapDelegate alloc] initWithMapView:self]];
The only method MapDelegate class has is
# MapDelegate.m ...
- (id)initWithMapView:(MapView *)aMapView {
self = [super init];
self.mapView = aMapView;
return self;
}
Having [self setShowsUserLocation:YES]; commented, all works fine - I see the map. If I uncomment this line, my application begins to crash.
What my MapDelegate class is missing?
UPDATE 1: if I don't use a separate class MapDelegate and set just setDelegate:self - all works.
UPDATE 2: Now I understand, that the problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that I need MapDelegate class to live longer than it does now (delegate property has weak attribute). If I do the following:
#property (strong) id delegateContainer;
....
[self setDelegateContainer:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateContainer];
...it works! Is there a better way of retaining MapDelegate life cycle along with the one of MKMapView?
Thanks!
After waiting enough for any answers that could appear here and ensuring original problematic behavior twice more times, I am posting my own answer based on the second update from the question:
The problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that MapDelegate class should be able to be kept alive outside of the scope of question's initWithFrame method because delegate property has weak attribute. The possible solution is to create an instance variable serving as a container for a delegate class, for example:
#property (strong) id delegateClass;
....
[self setDelegateClass:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateClass];
This solves the original problem.
LATER UPDATE
Though it is possible to set MKMapView's delegate in a separate class, I now realize that such model should not be used:
Currently I always prefer to use my controllers (i.e. controller layer in MVC in general) as delegates for all of my View layer classes (map view, scroll view, text fields): controller level is the place where all the delegates of different views can meet - all situated in controller layer, they can easily interact with each other and share their logic with the general logic of your controller.
On the other hand, if you setup your delegate in a separate class, you will need to take additional steps to connect your separate delegate with some controller, so it could interact with a rest part of your logic - this work have always led me to adding additional and messy pieces of code.
Shortly: do not use separate classes for delegates (at least view classes delegates provided by Apple), use some common places like controllers (fx for views like UIScrollView, MKMapView, UITableView or models like NSURLConnection).
I think viewDidLoad would be a better place to set up the map view. It's just a guess, but perhaps the crash is due to the view not being loaded yet.
Of course subclassing MKMapView isn't recommended at all. You would generally put your map as a subview, and set the main view to be the delegate. From the docs:
Although you should not subclass the MKMapView class itself, you can get information about the map view’s behavior by providing a delegate object.
Finally, if you really want to have a separate delegate class, you don't need to set its mapView, as all delegate methods pass the map as an argument.
I have a UITableView with a list of items. Selecting an item pushes a viewController that then proceeds to do the following. from method viewDidLoad I fire off a URLRequest for data that is required by on of my subviews - a UIView subclass with drawRect overridden. When the data arrives from the cloud I start building my view hierarchy. the subclass in question gets passed the data and it's drawRect method now has everything it needs to render.
But.
Because I don't call drawRect explicitly - Cocoa-Touch handles that - I have no way of informing Cocoa-Touch that I really, really want this UIView subclass to render. When? Now would be good!
I've tried [myView setNeedsDisplay]. This kinda works sometimes. Very spotty.
I've be wrestling with this for hours and hours. Could someone who please provide me with a rock solid, guaranteed approach to forcing a UIView re-render.
Here is the snippet of code that feeds data to the view:
// Create the subview
self.chromosomeBlockView = [[[ChromosomeBlockView alloc] initWithFrame:frame] autorelease];
// Set some properties
self.chromosomeBlockView.sequenceString = self.sequenceString;
self.chromosomeBlockView.nucleotideBases = self.nucleotideLettersDictionary;
// Insert the view in the view hierarchy
[self.containerView addSubview:self.chromosomeBlockView];
[self.containerView bringSubviewToFront:self.chromosomeBlockView];
// A vain attempt to convince Cocoa-Touch that this view is worthy of being displayed ;-)
[self.chromosomeBlockView setNeedsDisplay];
Cheers,
Doug
The guaranteed, rock solid way to force a UIView to re-render is [myView setNeedsDisplay]. If you're having trouble with that, you're likely running into one of these issues:
You're calling it before you actually have the data, or your -drawRect: is over-caching something.
You're expecting the view to draw at the moment you call this method. There is intentionally no way to demand "draw right now this very second" using the Cocoa drawing system. That would disrupt the entire view compositing system, trash performance and likely create all kinds of artifacting. There are only ways to say "this needs to be drawn in the next draw cycle."
If what you need is "some logic, draw, some more logic," then you need to put the "some more logic" in a separate method and invoke it using -performSelector:withObject:afterDelay: with a delay of 0. That will put "some more logic" after the next draw cycle. See this question for an example of that kind of code, and a case where it might be needed (though it's usually best to look for other solutions if possible since it complicates the code).
If you don't think things are getting drawn, put a breakpoint in -drawRect: and see when you're getting called. If you're calling -setNeedsDisplay, but -drawRect: isn't getting called in the next event loop, then dig into your view hierarchy and make sure you're not trying to outsmart is somewhere. Over-cleverness is the #1 cause of bad drawing in my experience. When you think you know best how to trick the system into doing what you want, you usually get it doing exactly what you don't want.
I had a problem with a big delay between calling setNeedsDisplay and drawRect: (5 seconds). It turned out I called setNeedsDisplay in a different thread than the main thread. After moving this call to the main thread the delay went away.
Hope this is of some help.
The money-back guaranteed, reinforced-concrete-solid way to force a view to draw synchronously (before returning to the calling code) is to configure the CALayer's interactions with your UIView subclass.
In your UIView subclass, create a displayNow() method that tells the layer to “set course for display” then to “make it so”:
Swift
/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
public func displayNow()
{
self.layer.setNeedsDisplay()
self.layer.displayIfNeeded()
}
Objective-C
/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
- (void)displayNow
{
[self.layer setNeedsDisplay];
[self.layer displayIfNeeded];
}
Also implement a draw(_: CALayer, in: CGContext) method that'll call your private/internal drawing method (which works since every UIView is a CALayerDelegate):
Swift
/// Called by our CALayer when it wants us to draw
/// (in compliance with the CALayerDelegate protocol).
override func draw(_ layer: CALayer, in context: CGContext)
{
UIGraphicsPushContext(context)
internalDraw(self.bounds)
UIGraphicsPopContext()
}
Objective-C
/// Called by our CALayer when it wants us to draw
/// (in compliance with the CALayerDelegate protocol).
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
UIGraphicsPushContext(context);
[self internalDrawWithRect:self.bounds];
UIGraphicsPopContext();
}
And create your custom internalDraw(_: CGRect) method, along with fail-safe draw(_: CGRect):
Swift
/// Internal drawing method; naming's up to you.
func internalDraw(_ rect: CGRect)
{
// #FILLIN: Custom drawing code goes here.
// (Use `UIGraphicsGetCurrentContext()` where necessary.)
}
/// For compatibility, if something besides our display method asks for draw.
override func draw(_ rect: CGRect) {
internalDraw(rect)
}
Objective-C
/// Internal drawing method; naming's up to you.
- (void)internalDrawWithRect:(CGRect)rect
{
// #FILLIN: Custom drawing code goes here.
// (Use `UIGraphicsGetCurrentContext()` where necessary.)
}
/// For compatibility, if something besides our display method asks for draw.
- (void)drawRect:(CGRect)rect {
[self internalDrawWithRect:rect];
}
And now just call myView.displayNow() whenever you really-really need it to draw (such as from a CADisplayLink callback). Our displayNow() method will tell the CALayer to displayIfNeeded(), which will synchronously call back into our draw(_: CALayer, in: CGContext) and do the drawing in internalDraw(_: CGRect), updating the visual with what's drawn into the context before moving on.
This approach is similar to #RobNapier's above, but has the advantage of calling displayIfNeeded() in addition to setNeedsDisplay(), which makes it synchronous.
This is possible because CALayers expose more drawing functionality than UIViews do— layers are lower-level than views and designed explicitly for the purpose of highly-configurable drawing within the layout, and (like many things in Cocoa) are designed to be used flexibly (as a parent class, or as a delegator, or as a bridge to other drawing systems, or just on their own). Proper usage of the CALayerDelegate protocol makes all this possible.
More information about the configurability of CALayers can be found in the Setting Up Layer Objects section of the Core Animation Programming Guide.
I had the same problem, and all the solutions from SO or Google didn't work for me. Usually, setNeedsDisplay does work, but when it doesn't...
I've tried calling setNeedsDisplay of the view just every possible way from every possible threads and stuff - still no success. We know, as Rob said, that
"this needs to be drawn in the next draw cycle."
But for some reason it wouldn't draw this time. And the only solution I've found is calling it manually after some time, to let anything that blocks the draw pass away, like this:
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(0.005 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[viewToRefresh setNeedsDisplay];
});
It's a good solution if you don't need the view to redraw really often. Otherwise, if you're doing some moving (action) stuff, there is usually no problems with just calling setNeedsDisplay.
I hope it will help someone who is lost there, like I was.
You can use CATransaction to force a redraw:
[CATransaction begin];
[someView.layer displayIfNeeded];
[CATransaction flush];
[CATransaction commit];
Well I know this might be a big change or even not suitable for your project, but did you consider not performing the push until you already have the data? That way you only need to draw the view once and the user experience will also be better - the push will move in already loaded.
The way you do this is in the UITableView didSelectRowAtIndexPath you asynchronously ask for the data. Once you receive the response, you manually perform the segue and pass the data to your viewController in prepareForSegue.
Meanwhile you may want to show some activity indicator, for simple loading indicator check https://github.com/jdg/MBProgressHUD