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.
Related
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.
Im loading a root view controller in landscape mode at launch(no interface builders are used).
In viewDidLoad, I am adding subviews to root view controllers view, like this
- (void)viewDidLoad
{
[super viewDidLoad];
// self.view.
UIView *toolBar=[[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];
toolBar.backgroundColor=[UIColor darkGrayColor];
[self.view addSubview:toolBar];
//code contiues...
}
but self.view.frame.size.width returns width of portrait mode instead of landscape.
thanks in advance
EDIT:
Implement the -loadView method:
- (void)loadView
{
self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
Also normally the parent view controller is responsible for setting the frame size of its children view controllers views. In the case of the root view controller, I believe it takes the size of the UIWindow it's attached to (so if you set the window size in you app delegate, you can just use [[UIView alloc] init] and then set the autoresizing mask in the -loadView method).
You might need to take the status bar into account in the above code, depending on your own code.
may be, portrait orientations are first in your project file (click to project in xcode, info tab in target, unit Supported interface orientations) ? If so, your app can launched in portrait orientation, then send viewDidLoad and rotate to landscape only after this.
When you do not use Interface Builder/xib files and neither create your view manually within loadview, the systems creates one with the maximum dimensions. This is stated in the loadview method documentation:
If the view controller does not have an associated nib file, this
method creates a plain UIView object instead.
This system generated UIView object has a transform property that is not the identiy transformation and thus you are not allowed to rely on the values from the frame property as stated here:
http://developer.apple.com/library/ios/documentation/uikit/reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instp/UIView/transform
Warning: If this property is not the identity transform, the value of
the frame property is undefined and therefore should be ignored.
So, what should you to instead? I guess the best solution is to use the bounds property of you UIView. This contains the already rotated coordinates.
I'm wanting to monitor for up, down, left, or right swipe gestures from an external class (i.e. with the methods not in my view controller). I've managed to set this up using an external class and properties to judge which direction was pushed, but I'm now wanting to run a method inside the view controller when a swipe is detected (which will accept which direction was swiped, and act accordingly).
I'm unsure how to get a method in one class to run when a swipe is detected in another. At present, my SwipeDetector class is set up as shown below, and I'd like those kDirectionKey constants to be fed into a method in the view controller class, and for that method to fire whenever a swipe takes place. Is this something I should be using observers for? I've never used them before, seem a little daunting.
#synthesize up = _up;
#synthesize down = _down;
#synthesize left = _left;
#synthesize right = _right;
#synthesize swipedDirection = _swipedDirection;
- (void)recogniseDirectionSwipes
{
_up = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(upSwipeDetected)];
_down = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(downSwipeDetected)];
_left = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(leftSwipeDetected)];
_right = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(rightSwipeDetected)];
_up.direction = UISwipeGestureRecognizerDirectionUp;
_down.direction = UISwipeGestureRecognizerDirectionDown;
_left.direction = UISwipeGestureRecognizerDirectionLeft;
_right.direction = UISwipeGestureRecognizerDirectionRight;
}
- (void)upSwipeDetected
{
NSLog(#"Direction swipe sniffed out, and that direction was up!");
_swipedDirection = kDirectionKeyUp;
}
- (void)downSwipeDetected
{
NSLog(#"Direction swipe sniffed out, and that direction was down!");
_swipedDirection = kDirectionKeyDown;
}
- (void)leftSwipeDetected
{
NSLog(#"Direction swipe sniffed out, and that direction was left!");
_swipedDirection = kDirectionKeyLeft;
}
- (void)rightSwipeDetected
{
NSLog(#"Direction swipe sniffed out, and that direction was right!");
_swipedDirection = kDirectionKeyRight;
}
#end
If you're doing sophisticated gesture detection on a UIView, it would make sense to do that in the UIViewController's view. To encapsulate that functionality you would create a UIView subclass, implement your gesture handling there, then pass appropriate messages back to the controller class as needed.
The latter seems to be your main question. That's a classic case for the delegation pattern. If you opt to create a custom UIView to implement the gesture handling, let's call it FooView then you could create a formal protocol FooViewDelegate to handle messages to the view's delegate. In this case, the delegate would be your controller class. Apple docs on protocols.
Alternatively, you could just implement the gesture detection in your UIViewController subclass and no have to worry about delegation. It depends on your requirements.
As another alternative (one to which you allude), if the view controller retains a reference to the SwipeDetector class, you could observe properties on the SwipeDetector instance.
[self addObserver:self forKeyPath:#"swipeDetector.swipeDirection"
options:NSKeyValueObservingOptionNew
context:NULL];
Note that for KVO to work, you need to use the property accessors on your SwipeDetector class, e.g. self.swipeDirection = kDirectionKeyUp; instead of setting the ivars directly.
Two objective-c methods, -(void) viewDidLoad and -(void)loadView are methods called upon execution of a program but whats the different between them?
Do you mean viewDidLoad and loadView? viewDidLoad is a method called when your view has been fully loaded. That means all your IBOutlets are connected and you can make changes to labels, text fields, etc.
loadView is a method called if you're (typically) not loading from a nib. You can use this method to set up your view controller's view completely in code and avoid interface builder altogether.
You'll typically want to avoid loadView and stick to viewDidLoad.
Use -(void)loadView when you create the view. Typically usage is:
-(void)loadView {
UIView *justCreatedView = <Create view>;
self.view = justCreatedView;
}
Use -(void)viewDidLoad when you customize the appearance of view. Exapmle:
-(void)viewDidLoad {
self.view.backgroundColor = [UIColor blackColor];
...
}
i think you are talking about loadView and viewDidLoad.
loadView is a method that you not using a nib file - you use it to programmatically 'write' your interface
viewDidLoad fires automatically when the view is fully loaded. you can then start interacting with it.
more to read read in the discussion here iPhone SDK: what is the difference between loadView and viewDidLoad?
I want to draw text into UIView's subview using drawInRect:withFont:lineBreakMode call but that operates on the current context only.
Is it possible to draw text into a subview from current view?
The subview is a generic UIView instance and I don't really want to create a new UIView-derived class just for this purpose if I can avoid it.
One option would be to add a CALayer to the view's layer instead of adding a UIView to the view. The CALayer has a delegate property which you can assign any object to. The CALayer calls:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
on the delegate, which you can take to do something like:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
if (layer == myLayer) {
UIGraphicsPushContext(ctx);
[string drawInRect:rect withFont:font lineBreakMode:mode];
UIGraphicsPopContext();
}
}
No, if you're going to do something with a context, you have to be in that view's -drawRect:. You can always make your subview a UIView subclass that overrides -drawRect: to display the text you want... but at that point, you're kind of reinventing UILabel.
No, you can't do what you describe. Subclassing UIView is exactly the method you're supposed to use for this— there's nothing wrong with creating a UIView subclass which only has a simple -drawRect: method.