how do i clear a draw uiview? - objective-c

I am currently trying to refresh or clear my UIView draw but a lot of methods aren't working. I tried:
self.Draw.clearsContextBeforeDrawing
[self.Draw setNeedsDisplay];
self.Draw.removeFromSuperview;
My code:
-(void)drawPoint:(UITouch *)touch
{
PointLocation *currentLoc = [[PointLocation alloc] init];
currentLoc.location = [touch locationInView:self];
self.previousPoint = self.point;
self.point = currentLoc;
[self drawToBuffer];
[self setNeedsDisplay];
}
Is there any way to clear my uidraw? Or at least reload it?

There's a few basic problems here.
How is Draw declared? Follow cocoa naming conventions. Only Class names should be capitalized. Instances of a class should start with a lowercase letter.
I think what you want is:
#interface Draw: UIView
#end
#implementation Draw
- (void) drawRect: (CGRect) r {
// here you will draw whatever you want in your view.
}
#end
Draw* draw;
in your drawPoint method:
[draw setNeedsDisplay];
For more details see The View Drawing Cycle:
http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW136
For bonus points, name your view something more descriptive than Draw. It is a view of something, a window containing some content. It is not a verb.

Related

In UIViewController's code, [self.subViewGrid setNeedsDisplay] not calling -drawRect

I have an iPad app, using Storyboards, XCode 4.6 and iOS 6.1. I have a scene that contains a UIViewController. Inside that UIViewController, I have a UIScrollController, all created using IB. Programmatically, in viewDidLoad I created two (2) UIViews (one called subViewGrid, the other called subViewData) and added them to the UIViewController; they both display correctly in the Simulator. Here's the code:
- (void)viewDidLoad {
[super viewDidLoad];
// notify me when calendar has been tapped and CFGregorianDate has been updated
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(calendarTapNotification:)
name:#"calendarDateSelected" object:nil ];
// UIScrollVIew settings
CGSize scrollableSize = CGSizeMake(760, 1379); // set size of scheduleView
[self.schedScrollView setContentSize:scrollableSize];
self.schedScrollView.contentInset = UIEdgeInsetsMake(0,0,44,44); // allow for scroll bar
self.schedScrollView.directionalLockEnabled = YES; // prevents diagonal scrolling
// create a sub-view to hold the appointment GRID
CGRect frame = CGRectMake(0,0,760,1390); // 110,48,760,1390
subViewGrid = [[UIView alloc] initWithFrame:frame];
subViewGrid.tag = 12; // use tag to get correct sub-view
subViewGrid.backgroundColor = [UIColor grayColor];
subViewGrid.alpha = 1.0; // make it opaque
[self.schedScrollView addSubview:subViewGrid];
// create a sub-view to hold the appointment DATA
frame = CGRectMake(110,48,670,750);
subViewData = [[UIView alloc] initWithFrame:frame];
subViewData.tag = 22; // use tag to get correct sub-view
subViewData.backgroundColor = [UIColor redColor];
subViewData.alpha = 0.2; // make it sort of transparent
[self.schedScrollView addSubview:subViewData];
[self.subViewGrid setNeedsDisplay]; // **** UPDATED ****
}
Here is the .h file contents for the UIViewController:
#interface CalendarViewController : UIViewController {
UIView *subViewGrid;
UIView *subViewData;
}
#property (strong, nonatomic) IBOutlet UIScrollView *schedScrollView;
- (void) calendarTapNotification:(NSNotification *) notification;
-(NSDate *)beginningOfDay:(NSDate *)date;
-(NSDate *)endOfDay:(NSDate *)date;
#end
In my drawRect method, I have some code that is supposed to draw a "grid" on the subViewGrid. The problem is drawRect never gets called.`
I have read the UIView Programmer's Guide and looked in SO and did a Google search, but found nothing that addresses the issue, which is: why won't [self.subViewGrid setNeedsDisplay] call drawRect from where I have it placed?
Your view controller needs to call setNeedsDisplay for the view it controls, not for itself. So, you want
[self.subViewGrid setNeedsDisplay]
This is just an error in your reading the documentation. Understanding the documentation is critical for objective-C programming so I'll try to help you get a grasp of it.
If you look at the documentation for setNeedsDisplay you will see that it is either a CALayer or UIView class method. If you then look at inheritance, you will see that UIView is UIResponder:NSObject and CALayer is NSObject. None of these inherit from UIViewController which is why you are getting the error. You need to call [self.subViewGrid setNeedsDisplay]

Non-rectangular 'clickable' area

I have a 3D map with many different shaped buildings. I would like users to click a building on the map, which would act as a segue to a new view.
I planned to go about this by creating invisible buttons the same shapes as the buildings, and then laying them on top of the imageview (which holds the map).
After doing some reading I found that it isn't as simple as I thought to create custom buttons (I think that I will have to do a lot of sublcassing and customization, which doesn't seem reasonable when I have 50+ different shaped buttons to make), so I was wondering if there was another aproach I could use to solve this problem.
Edit: I should add that right now that all functionality is working, but I am having to use default rectangular buttons, with alpha set to 0.1.
EDITED TO IMPROVE DATAMODEL
To do this you will have a UIView with your map image in the background. You can do this with either a UIImageView or by rendering the image yourself in drawRect.
You will then define several CGPath references. One for each building by doing something like this... How to create CGPathRef from Array of points The points will be the corners of each building.
Now store these paths in an array somehow. You will need one path for every "clickable"
building.
I would store the paths inside a Building object or something...
#interface Building : NSObject
#property (nonatomic) CGPath path;
#end
Now in the UIView subclass you override - (void)touchesBegan.... You can then get the touch point and iterate through your paths to find which one is touched...
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
for (Building *building in self.buildings) {
if (CGPathContainsPoint(building.path, CGAffineTransformIdentity, touchPoint, YES)) {
//the touch was inside this building!
//now do something with this knowledge.
}
}
}
I had once to implement a meteo application.
We did a map - with several clickable regions.
The easiest way to define those non-rectangular areas is to define them as UIBezierPath, and use a UITapGestureRecognizer
UIBezierPath uses a CGPath, but it's pure Objective-C.
Other advantage on CGPath, you can easily fill / stroke these paths - using random color - which can be neat for seeing them when debugging
So
//in you .h, or in class extension
//an array of UIBezierPath you create
// each bezierPath represent an area.
#property (nonatomic, strong) NSArray *myShapes;
//in your .m
- (void)viewDidLoad
{
[super viewDidLoad];
//load stuff
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self.view addGesture:tap];
// ...
}
- (void)handleTap:(UITapGestureRecognizer *)tap
{
UIBezierPath *recognizedArea = nil;
//if your shapes are defined in self.view coordinates :
CGPoint hitPoint = [tap locationInView:self.view];
for(UIBezierPath *regionPath in self.myShapes){
if([regionPath containsPoint:tap]){
recognizedArea = regionPath;
break;
}
}
//handle touch for THIS specific area when recognizedArea isn't nil
if(recognizedArea){
//YOUR CODE HERE
}
// you might also iterate with a normal integer increment
// and use the path index to retrieve region-specific-data from another array
}

Working on custom component: subclass UIView or UIViewController?

I'm working on a custom implementation of UISegmentedControl.
I'd like to create a component that able to receive config data and from which obtain a custom View similar to UISegmentedControl.
I started subclassing a UIView and i can create a custom UISegmentedControl with this code:
CustomSegment *segment = [[CustomSegment alloc]
initWithTitles:[NSArray arrayWithObjects:#"one",#"two",nil]];
[self.window addSubview:segment];
But now i'd like to improve my class and add some more customizable parameters to it.
For example i'd like add a custom separators, define the button fonts and so on... here my doubt:
Is it better to work on a UIView subClass or you suggest me to subclass a UIViewController, where i can manage View hierarchy in method like -(void)loadView and -(void)viewDidLoad ?
In a simple UIView subclass, when i launch the custom init method, i setup immediately subviews... while using a UIViewController i can call custom init and define how my subview is builded into -(void)loadView.
Don't use an UIViewController, just extend the UIView class like you did and keep extending its functionality.
Remember to save a pointer to each subview you add (i.e. buttons) in order to be able to access them later.
Define custom setters, for example, a custom setter for changing a button label title would be:
- (void) setButton1Title:(NSString*)str forState:(UIControlState)state{
//You can add some control here
if ([str length] > 20) return;
[_button1 setTitle:str forState:state]; //_button1 is my reference to the button
}
And so on. Don't provide direct access to your subviews, use methods instead.
Also, you can use "layoutSubviews" method to define how your views are going to be displayed in your custom view.
Hope it helps you.
Edit: In your case, I don't see why using lauoutSubviews method but I want to show you what I was trying to say.
Lets say that for example I need to create an UIView class to represent a "Contact" object in my application.
This is what I would do:
#interface ContactView : UIView{
UILabel* _nameLabel;
UILabel* _ageLabel;
Contact* _contact;
}
#property (retain) Contact* contact;
#end
#implementation ContactView
#synthetize contact = _contact;
-(id)initWithContact:(Contact*)c{
self = [super init];
if (self) {
_nameLabel = [[UILabel alloc] init];
_nameLabel.frame = CGRectZero;
[self addSubview:_nameLabel];
[_nameLabel release];
_ageLabel = [[UILabel alloc] init];
_ageLabel.frame = CGRectZero;
[self addSubview:_ageLabel];
[_ageLabel release];
self.contact = c;
}
}
- (void) layoutSubviews{
[super layoutSubviews];
_nameLabel.frame = CGRectMake(0.0f, 0.0f, 200.0f, 25.0f);
_ageLabel.frame = CGRectMake(0.0f, 25.0f, 200.0f, 25.0f);
if (self.contact){
_nameLabel.text = self.contact.name;
_ageLabel.text = self.contact.age;
}else{
_nameLabel.text = #"Unavailable";
_ageLabel.text = #"Unavailable";
}
}
- (void) setContact:(Contact*)c{
self.contact = c;
[self layoutSubviews];
}
#end
Check out how the "layoutSubiews" is used to set the correct frame and data to the labels.
Usually, I use it a lot when creating custom UITableViewCells where you have to reuse the view.
Let me know if I'm being confusing.

Displaying an NSString on a Custom View

I have an interface that has an NSTextField, NSButton, and an NSView. When I type something in the NSTextfield and press the button, I want the text to be drawn in the NSView. So far I have everything connected and working, except for the view.
How can I connect the text and the view so that every time I press the button, the text is drawn to the view?
How can I connect the text and the view so that every time I press the button, the text is drawn to the view?
Views do their own drawing.
You need to give the view the string to draw, and then set the view as needing display. You'll do these in the action method that you wire the button up to.
First, your custom view class needs to have a property for the value (string, in this case) that it's going to display. From your action method, which should generally be on a controller object, send the view object a setFoo: message (assuming you named the property foo). That takes care of job one: The view now has the value to display.
Job two is even easier: Send the view a setNeedsDisplay: message, with the value YES.
That's it. The action method is two lines.
Of course, since views draw themselves, you also need your custom view to actually draw, so you need to implement the drawRect: method in that class. It, too, will be short; all you need to do is tell the string to draw itself.
Bindings
http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/CocoaBindings/Concepts/WhatAreBindings.html#//apple_ref/doc/uid/20002372-CJBEJBHH
For simplicity's sake I didn't mention this before, but the app also has a speech element to speak the string. This aspect of the program works fine, so just ignore any messages involving the SpeakAndDraw class (it's actually misnamed and only includes a speech method, nothing about drawing).
View.m
#import "View.h"
#implementation View
#synthesize stringToDraw;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setAttributes];
stringToDraw = #"Hola";
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
NSRect bounds = [self bounds];
[self drawStringInRect:bounds];
}
- (void)setAttributes
{
attributes = [[NSMutableDictionary alloc] init];
[attributes setObject:[NSFont fontWithName:#"Helvetica"
size:75]
forKey:NSFontAttributeName];
[attributes setObject:[NSColor blackColor]
forKey:NSForegroundColorAttributeName];
}
- (void)drawStringInRect:(NSRect)rect
{
NSSize strSize = [stringToDraw sizeWithAttributes:attributes];
NSPoint strOrigin;
strOrigin.x = rect.origin.x + (rect.size.width - strSize.width)/2;
strOrigin.y = rect.origin.y + (rect.size.height - strSize.height)/2;
[stringToDraw drawAtPoint:strOrigin withAttributes:attributes];
}
#end
SpeakerController.m
#import "SpeakerController.h"
#implementation SpeakerController
- (id)init
{
speakAndDraw = [[SpeakAndDraw alloc] init];
view = [[View alloc] init];
[mainWindow setContentView:mainContentView];
[mainContentView addSubview:view];
return self;
}
- (IBAction)speakText:(id)sender
{
[speakAndDraw setStringToSay:[text stringValue]];
[speakAndDraw speak];
[view setStringToDraw:[text stringValue]];
[view setNeedsDisplay:YES];
NSLog(#"%#", view.stringToDraw);
NSLog(#"%#", [view window]);
}
#end

About drawing using quartz 2D on iPhone

I have a view, that have a drawRect method, I know that this method is the only way I control the View to draw something on it. So, I try to my drawing logic in this way:
- (void)drawRect:(CGRect)rect {
//my drawing code...
}
In my view, I use the IB to link this class.
[myView setNeedsDisplay];
It works, so, I designed to have a Command object inside the drawRect method, so that I can drawing dynamically based on my Cmd. Here is the code in the View after I modified:
- (void)drawRect:(CGRect)rect {
self.cmdToBeExecuted = [[DrawingSomethingCmd alloc] init];
[self.cmdToBeExecuted execute];
}
My DrawingsomthingCmd:
#implementation DrawingSomethingCmd
-(void)execute{
//my drawing code;
}
It works too. But the question is, how can I assign the self.cmdToBeExecuted dynamically.
Also, I changed my drawRect like this:
- (void)drawRect:(CGRect)rect {
[self.cmdToBeExecuted execute];
}
Because I have this to link with the IB,
IBOutlet myDrawingView *myView;
but after I type [myView ... ...], it don't allow me to get the variable cmdToBeExecuted. I ready make my variable accessible in .h:
#property (nonatomic, retain) Command *cmdToBeExecuted;
and .m also:
#synthesize cmdToBeExecuted;
Don't initialize the command inside the drawing rect. Initialize a default (maybe in viewDidLoad? it depends on what you are doing) somewhere as the view is created and then update them dynamically however you are doing this, whenever the need arises. So:
- (void)drawRect:(CGRect)rect
{
[[self commandToBeExecuted] execute];
}
and elsewhere:
// dynamically update the drawing
[myView setCommandToBeExected:[[[DrawingSomethingCommand alloc] init] autorelease]];