1.One is CGFloat.It’s a floating point number.All floating point numbers that have to do with drawing on the screen or getting touch events or whatever are CGFloats.This might be a double.It might be just a regular floating point number.Not only using CGFloat to specify positions no screen and all the thing s,but if you’re going to be multiplying or adding numbers to things are on screen to recalculate new things,all you want to do all that in the CGFloat domain.So you’re going to have a lot of properties and local variables that are CGFloats when you’re doing screen stuff.(7:00)
2.Then there’s a c-struct called CGPoint.That has just got two elements in it.X and y.That’s an X and Y position.And there’s CGSize which is just a struct with width and height which are both CGFloats also.And that’s just specifying a width and height.And then there’s CGRect which is c-struct with those other two CG structs in them.CGPoint and CGSize that specifies an origin and a width and height for a rectangle.(08:00)
3.And the reason for that is that views can be rotated.And if a view is rotated you can see that the rectangle that contains it might be much bigger.Right?Because it’s a diamond shape that it has to contain.Bottom line is understand that frame is a rectangle containing you in your superview’s coordinate system,bounds is the rectangle you use to draw when you’re drawing in your own code in your view.(11:00)
4.So let’s talk about the context.Mostly you don’t have to worry about context because you’re going to use UIBezierPath.But a context means where am I drawing in terms of — am I drawing on screen right now?Am I drawing off screen,in some bitmap?Am I creating a PDF file out of what I’m drawing?Am I drawing to printer?So that’s the context part of it.For normal drawing,UIKit sets this context up for you before it calls drawRect.And then once you’re in drawRect,context is ready to go.(18:00)
5.And remember that when we create views,things that go in the view camp,they want to be as generic as possible,as reusable as possible.(47:00)
6.So i’m doing setNeedsDisplay just to make sure,if anyone changes the rank or the suit or the faceUp-ness of my thing,I tell the system I need to be redrawn.(49:00)
7.And i’m going to have that rounded rect be as big as possible,so I specify self.bounds as the rectangle to draw this roundedRect in.Self.bounds is my coordinate system.Its width and height is the amount of space I have on screen to draw in.(50:00)
8.First thing I’m gonna to do this roudedRect actually,is I’m gonna clip to it,because I don’t want to draw outside that roundedRect.Okay?That’s the interior of my card,so I’m just going to clip to that.It also lets me do something like this.UIColor whiteColor,set my fill color,and then I can just use this C function,UIRectFill,and you specify a rectangle,self.bounds.Okay,this just fills this rectangle.And this is going to make a big rectangle,but this clip is going to keep the white of this on the inside of that roundedRect.So it’s not going to draw the white in the corners up there.(52:00)
9.I’m also going to do that contentMode = UIViewContentModeRedraw.Remember I told you that if my bounds ever change,I want to get my drawRect called.And then,really importantly,I want to make sure I do this in awakeFromNib.Because,in fact,in this demo,I am going to be creating this view in a storyboard.I’m not doing alloc init on it,so this is how I’m going to get set up.(53:00)
10.Hierarchical:A view has only one superview - (UIView *)superview.
But can have many(or zero)subviews - (NSArray *)subviews.Subview order(in subviews array matters):those later in the array are on top of those earlier .A view can clip its subviews to its bounds or not.
11.The top of this hierarchy for your MVC is the @property view!UIViewController’s @property(strong,nonatomic)UIView *view.This is the view whose bounds will be changed when autorotation happens,for example.This is the view you would programmatically add subviews to.All your MVC’s View’s UIView’s eventually have this view as their parent(it’s at the top).It is automatically hooked up for you when you drag out a View Controller in Xcode.
12.Yes,you might want to override UIView’s designated initializer.But you will also want to set up stuff in awakeFromNib.This is because initWithFrame: is NOT called for a UIView coming out of a storyboard!But awakeFromNib is.
Typical code...
-(void)setup{...} -(void)awakeFromNib{[self setup];} -(id)initWithFrame:(CGRect)aRect{ self = [super initWithFrame:aRect]; [self setup]; return self; }
13.Views have 3 properties related to their location and size.
@property CGRect bounds //your view’s internal drawing space’s origin and size
The bounds property is what you use inside your view own implementation.
It is up to your implementation as to how to interpret the meaning of bounds.origin.
@property CGPoint center;//the center of your view in your superview’s coordinate space @property CGRect frame;//a rectangle in your superview’s coordinate space which entirely contains your view’s bounds.size
14.How do you create a UIView in code(i.e. not in Xcode)?
Just use alloc and initWithFrame:(UIView’s designated initializer).
Can also use init(frame will be CGRectZero).
Example:
CGRect labelRect = CGRectMake(20,20,50,30); UILabel *label = [[UILabel alloc]initWithFrame:labelRect]; labe.text = @“Hello!”; [self.view addSubview:label];//Note self.view!
15.So how do I implement my drawRect:?
Use the Core Graphics framework directly(a C API,not object-oriented).Or we can use the object-oriented UIBezierPath class.
Core Graphics Concepts:
Get a context to draw into(IOS will prepare one each time your drawRect:is called)
Create paths(out of lines,arcs,etc.)
Set colors,fonts,textures,line widths,lineups,etc.
Stroke or fill the above-created paths.
UIBezierPath:Do all of the above,but capture it with an object.
Then ask the object to stroke or fill what you’ve created.
16.The context determines where your drawing goes:Screen,Offscreen Bitmap,PDF.Printer.For normal drawing,UIKit sets up the current context for you.But it is only valid during that particular call to drawRect:.A new one is set up for you each time drawRect: is called.So never cache the current graphics context in drawRect: to use later!
How to get this magic context?UIBezierPath draws into the current context,so you don’t need to get it if using that.But if you’re calling Core Graphics C functions directly,you’ll need it(it’s an argument to them).Call the following C function inside your drawRect: method to get the current graphics context.
CGContextRef context = UIGraphicsGetCurrentContext();
17.Define a Path:
//Begin the path: UIBezierPath *path = [[UIBezierPath alloc]init]; //Move around,add lines or arcs to the path: [path moveToPoint:CGPointMake(75,10)]; [path addLineToPoint:CGPointMake(160,150)]; [path addLineToPoint:CGPointMake(10,150)]; //Close the path(connects the last point back to the first) [path closePath];//not strictly required but triangle won’t have all 3 slides otherwise //Now that the path has been created,we can stroke/fill it.(Actually,noting has been drawn yet,we’ve just created the UIBezierPath). [[UIColor greenColor]setFill]; [[UIColor redColor]setStroke]; [path fill];[path stroke];
18.Can also set graphics state:e.g.path.lineWidth = 2.0 //line width in points(not pixels)
And draw rounded rects,ovals,etc.
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:(CGRect)bounds cornerRadius:(CGFloat)radius]; UIBezierPath *oval = [UIBezierPath bezierPathWithOvalInRect:(CGRect)bounds]; [roundedRect stroke]; [oval fill];
You can use a UIBezierPath to “clip“ your drawing:
[roundedRect addClip];//this would clip all drawing to be inside the roundedRect
19.What happens when views overlap?
As mentioned before,subviews list order determine’s who’s in front.
Lower ones(earlier in subviews array) can “show through”transparent views on top of them.
Also,you can hide a view completely by setting hidden property.
@property(nonatomic)BOOL hidden; myView.hidden = YES;//view will not be in screen and will not handle events
This is not as uncommon as you might think.On a small screen,keeping it de-cluttered by hiding currently unusable views make sense.Also this can be used to swap two(or more)views in and out depending on state.
20.Special considerations for defining drawing “subroutines"
What if you wanted to have a utility method that draws something?You don’t want that utility method to mess up the graphics state of the calling method.Use save and restore context functions.
-(void)drawGreenCircle:(CGContextRef)txt{ CGContextSaveGState(ctxt); [[UIColor greenColor] setFill]; //draw my circle CGContextRestoreGState(ctxt); } -(void)drawRect:(CGRect)aRect{ CGContextRef context = UIGraphicsGetCurrentContext(); [[UIColor redColor] setFill]; //do some stuff [self drawGreenCircle:context]; //do more stuff and expect fill color to be red }
21.We can use a UILabel as a subview to draw text in our view,but there are certainly occasions where we want to draw text in our drawRect:.
To draw in drawRect:,use NSAttributedString.
NSAttributedString *text = …; [text drawAtPoint:(CGPoint)p];//NSAttributedString instance method added by UIKit.
How much space will a piece of text will take up when drawn?
CGSize textSize = [text size];//another UIKit NSAttributedString instance method
You might be disturbed that there are drawing methods in Foundation(a non=UI framework!).These NSAttributedString methods are defined in UIKit via a mechanism called categories.Categories are an Objective-C way to add methods to an existing class without subclassing.
22.Drawing Images:UIImageView is like UILabel for images.But again,occasionally you want to draw an image in your drawRect:.
Create a UIImage object from a file in your Resources folder
UIImage *image = [UIImage imageNamed:@“foo.jpg"]
Or create one from a named file or from raw data:
UIImage *image = [[UIImage alloc]initWithContentsOfFile:(NSString *)fullPath]; UIImage *image = [[UIImage alloc]initWithData:(NSDate *)imageData];
Or you can even create one by drawing with CGContext functions:
UIGraphicsBeginImageContext(GCSize); //draw with CGContext functions UIImage *myImage = UIGraphicsGetImageFromCurrentContext(); UIGraphicsEndImageContext();
Now blast the UIImage’s bits into the current graphics context:
UIImage *image = …; [image drawAtPoint:(CGPoint)p];//p is upper left of the image [image drawInRect:(CGRect)r];//scales the image to fit in r [image drawAsPatternInRect:(CGRect)patRect];//tiles the image into patRect
Aside:You can get a PNG or JPG data representation of UIImage
NSData *jpgData = UIImageJPEGRepresentation((UIImage *)myImage,(CGFloat)quality); NSData *pngData = UIImagePNGRepresentation((UIImage *)myImage);
23.By default,when your UIView’s bounds change,there is no redraw.Instead,the “bits” of your view will be stretched or squished or moved.
Often this is not what you want …Luckily,there is a UIView @property to control this!It can be set in Xcode.
@property(nonatomic)UIViewContentMode contentMode;
These content modes move the bits of your drawing to that location...
UIViewContentMode{Left,Right,Top,Right,BottomLeft,BottomRight,TopLeft,TopRight}
These modes stretch the bits of your drawing...
UIViewContentModeScale{ToFill,AspectFill,AspectFit}//bit stretching/shrinking
This content mode calls drawRect: to redraw everything when the bounds changes...
UIViewContentModeRedraw//it is quite often that this is what you want
Default is UIViewContentModeScaleToFill(stretch the bits to fill the bounds)
24.Gestures are recognized by the class UIGestureRecognizer.This class is “abstract”.We only actually use “concrete subclasses”of it.
There are two sides to using a gesture recognizer:
1.Adding a gesture recognizer to a UIView to ask it to recognize that gesture.
2.Providing the implementation of a method to ”handle” that gesture when it happens.
Usually #1 is done by a Controller
Though occasionally a UIView will do it to itself if it just doesn’t make sense without that gesture.
Usually #2 is provided by the UIView itself
But it would not be unreasonable for the Controller to do it.
Or for the Controller to decide it wants to handle a gesture differently than the view does.
25.Adding a gesture recognizer to a UIView from a Controller
-(void)setPannableView:(UIView *)pannableView //maybe this is a setter in a Controller { _pannableView = pannableView; UIPanGestureRecognizer *pangr = [[UIPangestureRecognizer alloc]initWithTarger:pannableView action:@selector(pan:)]; [pannableView addGestureRecognizer:pangr]; }
UIPanGestureRecognizer is a concrete subclass of UIGestureRecognizer that recognizes”panning”(moving something around with your finger).There are,of course,other concrete subclasses(for swipe,pinch,tap,etc.)
Note that we are specifying the view itself as the target to handle a pan gesture when it is recognized.Thus the view will be both the recognizer and the handler of the gesture.The UIView does not have to handle the gesture.It could be,for example,the Controller that handles it.The View would generally handle gestures to modify how the View is drawn.The Controller would have to handle gestures that modified the Model.
@selector(pan:) is the action method that will be sent to the target(the pannableView)during the handling of the recognition of this gesture.
If we don’t do this,then even though the pannableView implements pan:,it would never get called because we would have never added this gesture recognizer to the view’s list of gestures that it recognizes.Think of this as “turning the handling of this gesture on”.
Only UIView instances can recognize a gesture(because UIViews handle all touch input).
But ant object can tell a UIView to recognize a gesture(by adding a recognizer to the UIView).
And any object can handle the recognition of a gesture(by being the target of the gesture’s action).
26.How do we implement the target of a gesture recognizer?
Each concrete class provides some methods to help you do that.
For example,UIPanGestureRecognizer provides 3 methods:
-(CGPoint)translationInView:(UIView *)aView; -(CGPoint)velocityInView:(UIView *)aView; -(void)setTranslation:(CGPoint)translation inView:(UIView *)aView;
Also,the base class,UIGestureRecognizer provides this @property:
@property(readonly)UIGestureRecognizerState state;
Gesture Recognizers sit around in the state Possible until they start to be recognized.
Then they either go to Recognized(for discrete gestures like a tap)
Or they go to Began(for continuous gestures like a pan)
At any time,the state can change to Failed(so watch out for that)
If the gesture is continuous,it’ll move on to the Changed and eventually the Ended state
Continuous can also go to Cancelled state(if the recognizer realizes it’s not this gesture after all)