How to draw text in UIView after -drawRect has been called - objective-c

I have an iPad app, XCode 4.5, iOS 6.1 and Storyboards. I have a UIView, with two (2) UIViews embedded in it. The first UIView (called Calendar) is on the top half, and the second UIView (called Schedule) is on the bottom half. I create a calendar on the top half, and draw a grid to display a schedule on the bottom half.
The sequence of events is the user is presented with the scene with the Calendar (completely filled in for current month) and Schedule (an empty grid is displayed). When a day is tapped on the Calendar, the schedule for that day will be displayed in the Schedule view.
The problem is -drawRect is called when the scene is initially displayed, before the user has the ability to choose a date to display for the schedule. Since I have to do the drawing from within -drawRect, I can't figure out how to fill in the schedule outside of -drawRect. I was wondering if using setNeedsDisplayInRect:, would accomplish what I need to do?
I have looked at SO and Google for several days now, and found nothing that would answer this particular question. This is NOT a duplicate of this question, but rather a follow-on since the project has been redefined. Any ideas would be gratefully appreciated.
UPDATE: here is my code using setNeedsDisplayInRect:
- (void) drawSchedule {
const float rectWidth = 120.0;
// draw detail for selected day for each staff member
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextSetRGBFillColor(context, 0, 0, 1, 0.3);
UIGraphicsBeginImageContext(self.bounds.size);
// draw customer name using bounds from CGRectMake
const float nameFontSize = 12;
UIFont *hourfont=[UIFont systemFontOfSize: nameFontSize];
[[UIColor blackColor] set];
[self setNeedsDisplayInRect: CGRectMake(160.0, 150.0, 120.0, 50.0)]; // mark as needs to be redrawn
// customer for this time slot
[#"John Doe" drawAtPoint:CGPointMake(114, 40) withFont:hourfont];
}

When a day is tapped on the Calendar, the schedule for that day will be displayed in the Schedule view.
When you user taps the a day on the calendar, tell the Schedule to change:
[self.calendarView displayDate: thatDay];
The Schedule, in turn, gets the message, figures out what it needs to do, and tells itself to redisplay when its time to draw.
- (void) displayDate: (YourDateObject*) thatDay
{
self.date=thatDay;
[self setNeedsDisplay: YES]
}
Now, the system will call the Schedule's drawRect: at the appropriate time, and you'll display the new schedule.
The general rule is simply this: set the state of the view when you know it, and let the view redisplay itself when asked. Don't try to say, "Draw this"; instead, say, "Here is what you should draw next time. As soon as you're ready, raise your hand; the teacher will call on you."

Perhaps you can use UIView's method: setNeedsDisplay. This will cause drawRect: to be called again. Or, if you only want to redraw a portion of the view you could use setNeedsDisplayInRect:, as you mentioned.
You should never call drawRect: yourself.
UIGraphicsBeginImageContext(self.bounds.size)
[#"John Doe" drawAtPoint:CGPointMake(114,40) withFont:hourFont];
UIimage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
Then you can use imageView to add it as a sub-view to your view.

Typically, you should do all of your drawing within the drawRect: method. When a user taps on a date in the calendar, you only need to send the setNeedsUpdate message to the schedule view. Then the schedule view will automatically redraw and call its own drawRect: method.
Calling setNeedsDisplayInRect: (or setNeedsDisplay) simply notifies the system that the receiver needs to be redrawn. Your drawRect: method will be invoked at some point later, when it is appropriate to perform the actual drawing.

This question is moot; you can't clear the drawing on a UIView, which makes this design not viable.
I am now considering using UITableViews or a UIView sub-view on top of the schedule grid, which brings up new issues.
Thank you everybody for your comments.

Related

iOS 8 custom view controller presentation: changing size of presented VC during animation

I'm writing an app with a series of cards in a table view, similar to the layout of the Google app for iOS when Google Now cards are enabled. When the user taps on a card, there should be a custom transition to a new view controller which is basically the card looking bigger, almost filling the screen, and it has more details on it. The custom transition itself should look like the card is animated upward and growing in size until it reaches its final size and position, which is now the new view controller holding the card.
I have been trying to approach this using a custom view controller transition. When the card is tapped, I initiate a custom view controller transition with UIModalPresentationCustom, and I set a transition delegate, which itself vends a custom animator and a custom UIPresentationController. In animateTransition:, I add the new view controller's view into the container view, setting the frame to the card's frame initially (so it looks like the card is still there and unchanged). Then I attempt to perform an animation where the presented view's frame grows in size and changes in position so that it moves into its final position.
Here's some of my code which does what I've described above - I'm trying to keep it short and sweet, but I can provide more info if needed:
Transition Delegate
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// NOWAnimationDelegate is my own custom protocol which defines the method for asking the presenting VC for the tapped card's frame.
UIViewController<NOWAnimationDelegate> *fromVC = (UIViewController<NOWAnimationDelegate> *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *finalVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
// Ask the presenting view controller for the frame of the tapped card - this method works.
toView.frame = [fromVC rectForSelectedCard];
[transitionContext.containerView addSubview:toView];
CGRect finalRect = [transitionContext finalFrameForViewController:finalVC];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toView.frame = finalRect;
}completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
Custom UIPresentationController
-(CGSize)sizeForChildContentContainer:(id<UIContentContainer>)container withParentContainerSize:(CGSize)parentSize {
return CGSizeMake(0.875*parentSize.width, 0.875*parentSize.height);
}
-(CGRect)frameOfPresentedViewInContainerView {
CGRect presentedViewFrame = CGRectZero;
CGRect containerBounds = self.containerView.bounds;
presentedViewFrame.size = [self sizeForChildContentContainer:(UIView<UIContentContainer> *)self.presentedView withParentContainerSize:containerBounds.size];
presentedViewFrame.origin.x = (containerBounds.size.width - presentedViewFrame.size.width)/2;
presentedViewFrame.origin.y = (containerBounds.size.height - presentedViewFrame.size.height)/2 + 10;
return presentedViewFrame;
}
What I'm finding is happening is that the new view is being automatically set to its final size immediately at the start of the animation, and then the animation is just the new view animating upwards. Using breakpoints, I'm noticing that frameOfPresentedViewInContainerView is called during the [transitionContext.containerView addSubview:toView] call, which would probably explain why this is happening - frameOfPresentedViewInContainerView returns "The frame rectangle to assign to the presented view at the end of the animations" according to the UIPresentationController documentation.
However, I'm not sure how to proceed, or if it's even really possible. All the examples of custom view controller transitions I've seen have all had the final size of the presented view controller static and unchanging during the animation. Is there any way to perform a custom view controller transition with a changing size of the presented view during the animation, or do I have to approach this in a different way?
Basically what you need to do is animating your toView.transform using CGAffineTransform in your Transition Delegate. Steps to do:
Before animating set your toView.frame = your frame when it's not showing
Create, let's say, CGAffineTransformMakeScale to scale from hidden frame to your desired final presented frame
On animation block set toView.transform to the transform that you create on step 2
As Aditya mentions CGAffineTransform is the way to go here.
Ive got this working now with it maintaining the width:
CGFloat targetscale=initialHeight/finalHeight;
CGFloat targetyoffset=(finalHeight-(finalHeight*targetscale))/2;
int targety=roundf(initialPosition-targetyoffset);
CGAffineTransform move=CGAffineTransformMakeTranslation(0, targety);
CGAffineTransform scale=CGAffineTransformMakeScale(1,targetscale);
toView.transform=CGAffineTransformConcat(scale,move);
This positions the incoming view taking into account the determined scale and then performs both transforms concurrently so the view scales & moves to the final position and size.
You then just set
toView.transform=CGAffineTransformIdentity;
in the animation block and it'll scale to the final location and size.
Note, this only moves and scales in the vertical dimension but can be adapted to scale in all directions like so:
+(CGAffineTransform)transformView:(UIView*)view toTargetRect:(CGRect)rect{
CGFloat targetscale=rect.size.height/view.frame.size.height;
CGFloat targetxoffset=(view.frame.size.width-(view.frame.size.width*targetscale))/2;
int targetx=roundf(rect.origin.x-view.frame.origin.x-targetxoffset);
CGFloat targetyoffset=(view.frame.size.height-(view.frame.size.height*targetscale))/2;
int targety=roundf(rect.origin.y-view.frame.origin.y-targetyoffset);
CGAffineTransform move=CGAffineTransformMakeTranslation(targetx, targety);
CGAffineTransform scale=CGAffineTransformMakeScale(targetscale,targetscale);
return CGAffineTransformConcat(scale, move);
}
And don't forget to transform the cell's rect the global coordinate space so the start frame is correct for the whole window not just the cells position in the table.
CGRect cellRect=[_tableView rectForRowAtIndexPath:[_tableView indexPathForSelectedRow]];
cellRect=[self.tableView convertRect:cellRect toView:presenting.view];
hope this helps!
Apple provides a wwdc sample app 'LookInside', accompanying talk 228: 'A look inside presentation controllers'. It features 2 custom presentations, of which one animates the size of the presented view controller. A look inside that code should help you out ;)

Why NSView leaves an image on superview on where it was when I move it?

I am working on a small application on Mac that I need to create customed cursor and move it. I used NSImageView to implement it. However when I call setFrameOrigin (the same to setFrame) it will leaves images on the previous place.
Here is my code:
#property (nonatomic, strong) NSImageView *eraserView;
this is the define
_eraserView = [[NSImageView alloc] initWithFrame:CGRectMake(100, 100, 32, 32)];
_eraserView.image = [NSImage imageNamed:#"EraserCursor"];
[self.view addSubview:_eraserView];
[_eraserView setHidden:YES];
here is the initialization. Everything goes well until now but:
- (void)setImageatPoint:(NSPoint)point
{
[_eraserView setFrameOrigin:point];
}
- (void)hidePenImage
{
[_eraserView setHidden:YES];
}
- (void)unhidePenImage: (BOOL)isEraser
{
[_eraserView setHidden:NO];
}
These are methods I use to change the state of the NSImageView. They will be called by another class using delegate when corresponding events of trackpad occurs.
However every time I change the state of the NSImageView, it seems like it is drawn on the superview.
I debugged it and found there was no extra subviews. And when I use setHidden it has no effect on those tracks. I think it somehow did something to the CALayer, but I have no idea how to fix it.
Screenshots would help but in general if you move a view or change the area of the view that is drawn, you need to redraw.
To do this it kind of depends on how your drawing happens. Calling setNeedsDisplay may not be enough if your implementation of drawRect only draws a sub rect of the view bounds. Cocoa only draws what it is told to draw.
You can erase sections of the view that should be empty by drawing (filling) where it should be empty. That means drawing a color ( NSColor clearColor if nothing else) in the area that was previously drawn.

How to dynamically reposition UIButton as subview of UIImageView when rotating

I'm working on an iPad app that lets you control different things in a prototype of an intelligent house. For example it lets you turn lights on and off. For this, I have made a UIImageView that shows the floor plan of the house and added UIButtons as subviews for each lamp that can be toggled.
As you can see the buttons are placed perfectly on the floor plan using the setFrame method of each UIButton. However, when I rotate the iPad to portrait orientation, the following happens:
The buttons obviously still have the same origin, however it is not relative to the repositioning of the image.
The floor plan image has the following settings for struts and springs:
and has its content mode set to Aspect Fit.
My question is
how do I dynamically reposition each UIButton, such that it has the same relative position. I figure I have to handle this in the {did/should}AutorotateToInterfaceOrientation delegate method.
It should be noted that the UIImageView is zoomable and to handle this I have implemented the scrollViewDidZoom delegate method as follows:
for (UIView *button in _floorPlanImage.subviews) {
CGRect oldFrame = button.frame;
[button.layer setAnchorPoint:CGPointMake(0.5, 1)];
button.frame = oldFrame;
button.transform = CGAffineTransformMakeScale(1.0/scrollView.zoomScale, 1.0/scrollView.zoomScale);
}
Thank you in advance!
I find the best way to layout subviews is in the - (void) layoutSubviews method. You will have to subclass your UIImageView and override the method.
This method will automatically get called whenever your frame changes and also gets called the first time your view gets presented.
If you put all your layout code in this method, it prevents layout fragmentation and repetition, keeps your view code in your views, and most things just work by default.

dynamically creating UIImageViews in a custom class method call in xcode

i want to dynamically create and add to the view a uiimageview when a touch is registered within the frame of another uiimageview. i want these to stay on the screen and remain moveable when a touch is registered inside the frame of each uiimageview. here is the code i have now but it doesn't quite work right and is really glitchy(i.e. i can pick up a new view and it is created but it doesn't follow the touch properly as the touch is moved:
image = [UIImage imageNamed:#"1.png"];
UIImageView *onePieceCopy = [[UIImageView alloc] initWithImage:image];
onePieceCopy.frame = CGRectMake(currentPos.x, currentPos.y, 75, 75);
[self addSubview:onePieceCopy];
if (CGRectContainsPoint([onePieceCopy frame], position)) {
onePieceCopy.center = position;
[self bringSubviewToFront:onePieceCopy];
}
[onePieceCopy release];
this code is in a switch statement that is in a method that is called when a touch is registered inside the frame of a designated UIImageView.
the ideal result would be a system not unlike a map editor where you drag parts from a 'staging' area and onto the map, if that makes any sense. does anyone know how to do this or how i can better my code to get the desired result
WWDC 2011 (must be a registered developer). Watch "Session 104 - Advanced Scroll View Techniques". At about minute 40 they do a nice pick and place of a uiimage.

Draw an NSView into an NSGraphicsContext?

I have a CGContext, which I can turn into an NSGraphicsContext.
I have an NSWindow with a clipRect for the context.
I want to put a scrollview into the context and then some other view into the scrollview so I can put an image into it... However, I can't figure out how to attach the scrollview into the context.
Eventually the view will probably be coming from a nib, but I don't see how that would matter.
I've seen this thread, (http://lists.apple.com/archives/quartz-dev/2006/Nov/msg00010.html) But they seem to leave off the step of how to attach the view into the context, unless there's something obvious I'm missing.
EDIT:
The reason I'm in this situation is that I'm writing a Mozilla Plugin. The browser gives me a CGContext (Quartz) and a WindowRef (QuickDraw). I can turn the CGContext into an NSGraphicsContext, and I can turn the windowRef into an NSWindow. From another data structure I also have the clipping rectangle...
I'm trying to draw an image into that context, with scrollbars as needed, and buttons and other UI elements... so I need (want) an NSView...
You can't put a view into a graphics context. A view goes either into another view, or as the content view of a window.
You can draw a view into a context by setting that context as the current context and telling the view to draw. You might do this as a means of rendering the view to an image, but otherwise, I can't think of a reason to do it. (Edit: OK, being a Netscape plug-in is probably a good reason.)
Normally, a view gets its own graphics context in NSView's implementation of the lockFocus method, which is called for you by display, which is called for you by displayIfNeeded (only if the view needs display, obviously), which is called for you as part of the event loop.
You don't need to create a context for a view except in very rare circumstances, such as the export-to-an-image case I mentioned. Normally, you let the view take care of that itself.
A partial solution?
What I have done currently is create a nib with a button in an IKImageView inside an NSScrollView. I load this in my plugin.
Then, since I have the NSWindow, I can get the contentView of the window. Then, I add the scrollview as subview of contentView.
It appears, but there seems to be some coordinate confusion about where the origin is. (top vs bottom) and since I'm mucking with the contentview of the WHOLE WINDOW, I'm doing some stuff very globally that perhaps I should be doing more locally. Like, the view never disappears, even when you close the tab, or go to another tab. (it does close when you close the window of course)
So, does this sound like a reasonable way of doing this? it feels a bit ... kludgy...
For future generations (and me when I forget how I did this and Google leads me back to my own question) Here's how I'm doing this:
I have a NIB with all my views, I load this on start-up.
on SetWindow, I set the clip rect and actually do the attaching:
NP_CGContext* npContext = (NP_CGContext*) window->window;
NSWindow* browserWindow = [[[NSWindow alloc] initWithWindowRef:npContext->window] autorelease];
NSView* cView = [browserWindow contentView];
NSView* hitView = [cView hitTest:NSMakePoint(window->x + 1, clip.origin.y + 1)];
if (hitView == nil || ![[hitView className] isEqualToString:#"ChildView"])
{
return;
}
superView = [hitView retain];
[superView addSubview: topView];
[superView setNextResponder: topView];
[topView setNextResponder: nil];
[browserWindow makeFirstResponder: topView];
To make sure I only addSubView once, I have a flag...
And then in handleEvent, I actually draw, Because I'm using an IKImageView, I can use the undocumented method: [imageView setImage: image]; which takes an NSImage.
So far this seems to be working for me. Hopefully this helps someone else.