CALayer/UIView crashes when I touch the UIVIew - iphone

Iam using a custom layer for drawing on my UIView. And to invalidate and update the layer I need to set my view to be the delegate of my CALayer. When this is done it draws my text correctly but when the view receives touches the app crashes.
What I am a doing wrong?
- (void) layoutSubView {
if (drawLayer == nil) {
drawLayer = [[CALayer alloc] init];
[drawLayer setDelegate:self];
[[self layer] addSublayer:drawLayer];
}
[drawLayer setFrame:[self bounds]];
[drawLayer setNeedsDisplay];
}
Above is my code to setup the CALayer, and below is the overridden drawLayer method.
- (void) drawLayer:(CALayer *)layer
inContext:(CGContextRef)ctx {
if (layer == drawLayer) {
UIGraphicsPushContext(ctx);
[self drawTitle:ctx];
UIGraphicsPopContext();
}
}
Here is the stack.

You can't use a UIView object to be the delegate of a layer other than its own layer. Its stated here. You will need to assign some other object as its delegate.

Related

setNeedsDisplay does not always call drawRect

I've seen this question posted before but I cannot find the answer..
As expected, drawRect gets called automatically when the program launches. However when I call [self setNeedsDisplay] drawRect does not get called anymore and I cannot understand the reason why...
//DrawView.m
[self setNeedsDisplay];
-(void)drawRect:(CGRect)rect{
NSLog(# "Called_1");
//DRAW A PATH
}
Maybe using self is incorrect. I have also tried using DrawImage but still it doesn't work.
//DrawView.h
#interface DrawView : UIView {
UIImageView *drawImage;
}
//DrawView.m
-(id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
drawImage = [[UIImageView alloc] initWithImage:nil];
drawImage.frame = CGRectMake( 0, 0, self.frame.size.width, self.frame.size.height );
[self addSubview:drawImage];
}
return self;
}
Usually when you want to have a view redraw you should call:
[self setNeedsDisplay: YES];
Granted I have never build on iOS, on OSX this code works every time. Also, for example, if you want your delegate to call a redraw for a view named someView:
[someView setNeedsDisplay: YES];
Note: that YES is a macro defined by obj-c that is just a value of 1.

iOS switching view controllers depending on the device orientation

I'm developing an Augmented Reality application, everything worked properly till now that I need two different kind of visualization (AR and Map) depending on the device orientation. In particular the application should use the landscapeViewController when the device is in landscape mode while it should use another controller (named faceUpViewController ) when the device's orientation is "face up". I tried doing it with two simple view controllers and it works fine. The problem happens when the landscapeViewController uses the AR controller. The view is completely white and I don't understand why. Both the two controllers are "contained" by a Root View Controller. I'm doing everything by coding so without nib files. Here is the code:
RootViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
}
- (void)deviceOrientationDidChange:(NSNotification *)notification{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationLandscapeLeft) {
if (self.landscapeViewController.view.superview == nil) {
if (self.landscapeViewController == nil) {
LandscapeViewController *lvc = [[LandscapeViewController alloc] init];
self.landscapeViewController = lvc;
[lvc release];
}
[self.faceUpViewController.view removeFromSuperview];
[self.view addSubview:self.landscapeViewController.view];
}
}
if (orientation == UIDeviceOrientationFaceUp) {
if (self.faceUpViewController.view.superview == nil) {
if (self.faceUpViewController == nil) {
FaceUpViewController *fvc = [[FaceUpViewController alloc] init];
self.faceUpViewController = fvc;
[fvc release];
}
[self.landscapeViewController.view removeFromSuperview];
[self.view addSubview:self.faceUpViewController.view];
}
}
}
#end
LandscapeViewController.m
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
UIView *landscapeView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
landscapeView.backgroundColor = [UIColor yellowColor];
self.view = landscapeView;
[landscapeView release];
ARController *arC = [[ARController alloc] initWithViewController:self];
arC.landscapeViewController = self;
self.arController = arC;
[arC release];
}
//When the view appear present the camera feed
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[_arController presentModalARControllerAnimated:NO];
}
FaceUpViewController.m
- (void)loadView
{
UIView *faceUpView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
faceUpView.backgroundColor = [UIColor blueColor];
self.view = faceUpView;
[faceUpView release];
}
ARController.m Very simple version
- (id) initWithViewController:(UIViewController *)theView{
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
self.rootController = theView;
//Retrieve screen bounds
CGRect screenBounds = [[UIScreen mainScreen] bounds];
UIView *overlaidView = [[UIView alloc] initWithFrame: screenBounds];
self.overlayView = overlaidView;
[overlaidView release];
self.rootController.view = overlayView;
// Initialise the UIImagePickerController
UIImagePickerController *picker= [[UIImagePickerController alloc] init];
self.pickerController = picker;
[picker release];
self.pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
self.pickerController.cameraViewTransform = CGAffineTransformScale(
self.pickerController.cameraViewTransform, 1.0f, 1.12412f);
self.pickerController.showsCameraControls = NO;
self.pickerController.navigationBarHidden = YES;
self.pickerController.cameraOverlayView = _overlayView;
}
return self;
}
- (void)presentModalARControllerAnimated:(BOOL)animated{
[self.rootController presentModalViewController:[self pickerController] animated:animated];
self.overlayView.frame = self.pickerController.view.bounds;
}
#end
I say again that I'm doing everything by coding thereby without nib files.
I really appreciate any advice!
Thanks
The primary problem with adding and removing your "child" view controllers' views as you've done here is that the view controller life cycle methods (viewWillAppear:, viewDidAppear:, etc.) won't ever get called on your child view controllers. Containers like UINavigationController and UITabBarController have always known how to delegate methods like these appropriately to their children, but UIViewController didn't officially support the ability to nest view controllers under your own custom container before iOS 5. It was possible, but it took a lot more work to do it right.
If you want to stick with the approach of adding and removing subviews, you have two options:
Require iOS 5+, and call addChildViewController:, removeFromParentViewController,
transitionFromViewController:toViewController:duration:options:animations:completion:,
willMoveToParentViewController:, and
didMoveToParentViewController: as described in the Implementing a Container View Controller section of the UIViewController Class Reference.
To support older iOS versions, you'll have to override many of the methods of the UIViewController class and delegate those calls manually to your child view controllers to make them behave as expected. I'd pay particular attention to the sections titled, "Responding to View Events", and "Responding to View Rotation Events" in the UIViewController Class Reference.
A different approach for pre-iOS 5 support is to present your child view controllers using presentModalViewController:animated: rather than adding their views as subviews to a container. Apple describes this approach in the View Controller Programming Guide for iOS under the section, Creating an Alternate Landscape Interface. The advantage of this approach is that your child view controllers are officially supported as first-class members of the view controller hierarchy, so UIKit will automatically manage their life cycles appropriately. You won't have to override and delegate all those methods manually.
You might want to try getting your acceptance rate up a little bit - more people would be willing to help you.
Anyway, wild guess: in your root controller, try putting the contents of
deviceOrientationDidChange
into
deviceOrientationWillChange.

Rendering content after zoomed in for UIScrollView

I've looked around and it seems like people say you can re-render the data after you zoom in now that you know the scale factor for UIScrollView. I've also seen some posts about making your layer to a CATiledLayer and set the levelsOfDetailBias and levelsOfDetail.
What I have is a UIScrollView, and in it, a ResultsView which is a subclass of UIView:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CATiledLayer *tiledLayer = (CATiledLayer *)self.layer;
tiledLayer.levelsOfDetailBias = 3;
tiledLayer.levelsOfDetail = 3;
self.opaque = YES;
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
+ (Class)layerClass {
return [CATiledLayer class];
}
In my class where I have the UIScrollView and ResultsView, I do:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.ResultsView;
}
Is this enough to have the text rerendered (sharp)? Or do I need to implement something in
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
}
If so, I'm not sure how to do that. In my class with UIScrollView and ResultsView, in ResultsView (in the XIB), I just have a couple of UILabels and UIViews (UIViews used for headings/footers for the view). So I do not know how to redraw those from ResultsView. Although ResultsView has the UILabels and UIViews as children of it in the XIB, I'm not sure how I would redraw those from the ResultsView class, and what I would need to do anyway.
Or is this the wrong approach? Do I just need to resize the UILabel and UIView by the scale factor in the scrollViewDidEndZooming: delegate method? TIA
I think you are taking the wrong approach. If your view consists of UILabels and UIViews that do not do any custom drawing, I would not mess with using a CATiledLayer backed view. Instead implement the scrollViewDidEndZooming:withView:atScale: delegate method and do something like this:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
scale *= [[[self.scrollView window] screen] scale];
[view setContentScaleFactor:scale];
for (UIView *subview in view.subviews) {
[subview setContentScaleFactor:scale];
}
}
I have something small to add to the accepted solution that caused me a few minutes of pain just in case anyone else runs into the same thing.
Looping through all subviews in the view you're zooming and calling setContentScaleFactor doesn't take into account if any of the subviews themselves have subviews. If you have a more complicated setup like that, make sure to also loop through the subviews of those container views and call
[subview setContentScaleFactor:scale];
on each. I made a method that I can invoke to call setContentScaleFactor on all the subviews of a given view and call that for each "container view" on the screen.
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
scale *= [[[self.scrollView window] screen] scale];
[view setContentScaleFactor:scale];
for (UIView *subview in view.subviews)
{
[self setContentSizeForView:subview andScore:scale];
//Loop through all the subviews inside the subview
for(UIView *subSubview in subview.subviews)
{
[self setContentSizeForView:subSubview andScale:scale];
}
}
}
- (void) setContentSizeForView:(UIView*) view andScale:(float)scale
{
[view setContentScaleFactor:scale];
}

Touch-To-Focus on cameraOverlayView in iOS 5?

I used to have touch to focus on my cameraOverlayView (in UIImagePickerController), but once I updated to iOS 5 it doesn't work.
I used a custom view class which I applied to my view in cameraOverlayView.
I made sure everything is connected and applied in Interface Builder.
I don't want to show camera controls. (imagePicker.showsCameraControlls = NO).
This is my code in the OverlayView class:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UIView * previewView = [[[[[[[[[[
self.picker.view // UILayoutContainerView
subviews] objectAtIndex:0] // UINavigationTransitionView
subviews] objectAtIndex:0] // UIViewControllerWrapperView
subviews] objectAtIndex:0] // UIView
subviews] objectAtIndex:0] // PLCameraView
subviews] objectAtIndex:0]; // PLPreviewView
[previewView touchesBegan:touches withEvent:event];
NSLog(#"Should Focus");
}
Do you guys have a different tap-to-focus method on an overlay?
Or do you know how to fix this?
THANK YOU SO MUCH IN ADVANCE!
because ios5.0
Touch events are not getting forwarded to the view in the cameraOverlayView property of UIImagePickerController.
please changed you init code like this:
// self.cameraOverlayView = [[UIView alloc] init];
// [self.cameraOverlayView addSubview:previewView];
[self.view addSubview:mask];
[self.view bringSubviewToFront:previewView];

Manual positioning of titleLabel in a custom UIButton (using layoutSubviews)

i've create a UIButton subclass for my app and i need to manually reposition the titleLabel to be about 1/4 of the button's height. this post; http://forums.macrumors.com/showthread.php?t=763140 appears to directly address the same issue, and the solution is simple and perfectly understandable. however, i have failed to implement this because my override of layoutSubviews is never called. my button is static, so layoutSubviews only need be called the first time, but it never is. my code:
#interface MyButton : UIButton {}
#implementation MyButton
- (id)initWithFrame:(CGRect)frame
{
self = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
[[self layer] setCornerRadius:14.0f];
[[self layer] setBorderWidth:3.0f];
[[self layer] setMasksToBounds:YES];
[self setBackgroundColor:[UIColor redColor]];
[self setFrame:frame];
return self;
}
- (void)layoutSubviews
{
NSLog(#"layout subs\n");
[super layoutSubviews];
}
#end
moving the label would be no problem, but layoutSubviews is never called. i've also tried adding layoutIfNeeded, but it made no difference. what's even weirder, is i've tried to call [self layoutSubviews] directly from the constructor but layoutSubviews is still not called!. i'm starting to think this might even be a bug in SDK 3.1.3
can anyone help?!
In layoutSubviews, you can do layout changes to your subviews. Make sure that inside the method:
√ Call [super layoutSubviews]
x Do not change value or rect of self.frame here. If you do so, it would recursively call layoutSubviews.
√ Make sure you only change frame of its subviews.
From UIView Class Reference
to answer my own question, it seems i was subclassing incorrectly. taking the following approach results in layoutSubviews being called correctly:
+ (id)buttonWithFrame:(CGRect)frame {
return [[[self alloc] initWithFrame:frame] autorelease];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame])
{
...
[self setFrame:frame];
}
return self;
}
hope that helps others