I'm trying to programmatically create a window with custom contentView and one custom NSTextField control, but i'm having trouble getting this hierarchy of window and views to draw themselves.
I create a custom borderless window and override it's setContentView / contentView accessors. This seems to work fine and custom contentView's initWithFrame and drawRect methods get called causing contentView to draw itself correctly.
But as soon as i try programmatically adding custom NSTextField to contentView it doesn't get added or drawn. By saying custom i mean that i override it's designated initializer (initWithFrame:frame - only for custom font setting) and drawRect method which looks like this:
- (void)drawRect:(NSRect)rect {
NSRect bounds = [self bounds];
[super drawRect:bounds];
}
The custom contentView's initializer looks like this:
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self != nil) {
// i want to draw itself to the same
// size as contentView thus i'm using same frame
CustomTextField *textField = [[CustomTextField alloc] initWithFrame:frame];
[self addSubview:textField];
[self setNeedsDisplay:YES];
}
return self;
}
I've been strugling with this for few hours so any pointers are much appreciated. More code is available on request .)
Your -drawRect: override seems wrong to me, why would you deliberately ignore the passed in rect argument? Why is this necessary?
As for why the text field is not appearing, it's most likely because you have not configured it. When you create an NSTextField in code, you don't get the same thing as the default instance that you get when you drag a text field onto a view in IB. You will need to configure the NSTextField and its NSTextFieldCell to get the appearance you desire.
I am using a programmatically added text field that I configure like this:
_textField = [[NSTextField alloc] initWithFrame:textFieldRect];
[[_textField cell] setControlSize:NSSmallControlSize];
[_textField setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
[_textField setBezelStyle:NSTextFieldSquareBezel];
[_textField setDrawsBackground:YES];
[_textField setBordered:YES];
[_textField setImportsGraphics:NO];
[_textField setAllowsEditingTextAttributes:NO];
[_textField setBezeled:YES];
[_textField sizeToFit];
[self addSubview:_textField];
[_textField setFrame:textFieldRect];
[_textField setAutoresizingMask:NSViewMinXMargin];
Thanks for your answers!
I found my own problem. The reason why drawRect wasn't being called is because custom textfield was drawn outside the frame of content view. I guess i forgot to mention crucial detail that i'm drawing window centered on the screen thus it's frame was with x/y offset.
To fill the window with it's content view I init contentView with same frame as window (meaning the same (x;y) offset from window's (0;0) point).
Now i'm just unable to write to custom text field, but that's another problem I think I'm able to handle.
Related
Hi I am trying to draw strings in my UITableViewCell in iOS 7 with the following code
-(void)drawRect:(CGRect)rect{
[super drawRect:rect];
CGRect playerNameRect = CGRectMake(0, kCellY, kPlayerNameSpace, kCellHeight);
NSDictionary*dictonary = [NSDictionary
dictionaryWithObjectsAndKeys:
[UIColor hmDarkGreyColor], NSForegroundColorAttributeName,
kFont, NSFontAttributeName,
nil];
[self.playerName drawInRect:playerNameRect withAttributes:dictonary];
}
However I can not get anything to appear... self.playerName is not nil, and the playerNameRect is correct.
I was previously using the following code to do the same thing but was recently deprecated in iOS 7
[self.playerName drawInRect:playerNameRect withFont:kFont lineBreakMode:NSLineBreakByTruncatingTail alignment:NSTextAlignmentCenter];
What is also strange is I can not get anything to draw in drawRect on a UITableViewCell... The deprecated code works when I am drawingRect on just a UIView.
You shouldn't use UITableViewCell's drawRect method to perform custom drawing. The proper way to do it is to create a custom UIView and add it as a subview of your cell (as a subview of the contentView property). You can add the drawing code to this custom view and everything will work fine.
Hope this helps!
Check out these posts too:
Table View Cell custom drawing 1
Table View Cell custom drawing 2
Table View Cell custom drawing 3
As others said, don't use UITableViewCell's drawRect selector directly. By doing that, you're relying on implementation details of UITableViewCell, and Apple made no guarantee that such behaviour won't break in future versions, just as it did in iOS 7... Instead, create a custom UIView subclass, and add it as a subview to the UITableViewCell's contentView, like this:
#implementation CustomTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self.contentView addSubview:[[CustomContentView alloc]initWithFrame:self.contentView.bounds]];
}
return self;
}
#end
And the CustomContentView:
#implementation CustomContentView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
NSDictionary * attributes = #{
NSFontAttributeName : [UIFont fontWithName:#"Helvetica-bold" size:12],
NSForegroundColorAttributeName : [UIColor blackColor]
};
[#"I <3 iOS 7" drawInRect:rect withAttributes:attributes];
}
#end
Works like charm!
Try setting cell.backgroundColor = [UIColor clearColor] in init.
While I agree with the accepted answer, here's my take on it for the records:
If you don't need any of the builtin UITableViewCell functionality (swiping, removing, reordering, ...) and just use it as a container to draw your custom stuff, then you might want to consider removing all of the cells subviews in tableview:willDisplayCell:ForRowAtIndexPath. This will make your drawing be visible again and will get you maximum performance (since you get rid of the subviews you don't need).
I have a custom subclass of NSSearchField that I would like to set the background color of.
#interface CustomNSSearchField : NSSearchField
#end
So far, I have tried:
Attempt #1
#implementation CustomNSSearchField
- (void)drawRect:(NSRect)rect
{
[super drawRect:rect];
[self setDrawsBackground:YES];
[self setBackgroundColor:[NSColor redColor]];
}
which resulted in no visual changes at all:
I then followed the suggestions here and also tried:
Attempt #2
- (void)drawRect:(NSRect)rect
{
[super drawRect:rect];
[[NSColor redColor] setFill];
NSRectFill(rect);
}
Which results in this:
How do I set the background color inside the bounds and behind the text of the search field?
You have to redraw the entire thing.
There is no property, to specifically change the background-color of the NSSearchField.
Check out this example:
Custom NSSearchField
Edit:
Also what's worth to point out.
You should never override the controls drawRect method.
You should rather make a subclass of NSSearchFieldCell.
I have a UIViewController called HomeViewController and it has a model that contains an array of data. HomeViewController also has a button that when pressed will show a UITableViewController that display's the model's array of data.
My question is, what is the standard/best way to set the popover's size? I only want the popover to be tall enough to display the data, but no taller. What is common practice? I assume that I need to set contentSizeForViewInPopover at some point but I'm not sure where...In the viewDidLoad method of the popover class? In the prepareForSegue method?
Here's the code I have so far: (Note that DataPresentingViewController is my popover view controller class`)
//In HomeViewController
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.destinationViewController isKindOfClass:[DataPresentingViewController class]])
{
DataPresentingViewController *dest = (DataPresentingViewController *) segue.destinationViewController;
dest.mySavedData = self.myModel.mySavedData;
}
}
I know that I could just set the contentSizeForViewInPopover here in the prepareForSegue method; however, this seems like something the popover class should deal with.
As of iOS7 contentSizeForViewInPopover is deprecated. You should use UIViewController's preferredContentSize property instead.
Hope this helps!
set contentSizeForViewInPopover in ViewDidLoad method in HomeViewController
contentSizeForViewInPopover is deprecated in iOS 7
The property: popoverContentSize of UIPopoverController represents the size of the content view that is managed by the view controller in the contentViewController property of UIPopoverController.
Reference
The advantage is that it is available in iOS 3.2 and later, so you don't need to check device version everytime, just replace contentSizeForViewInPopover method with your UIPopOverController object instance.
Have you tried:
setPopoverContentSize:<#(CGSize)#> animated:<#(BOOL)#>
in your segue logic block. Useful if you want the popover size to be variable based upon a data set, or view placement or whatever.
// self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0); //Deprecated in ios7
self.preferredContentSize = CGSizeMake(320.0, 600.0); //used instead
A little known trick to sizing your UIPopoverViewController to match the height of your UITableView is to use the tableView's rectForSection method to give you the height. Use the height in your viewController's contentSizeForViewInPopover like this:
- (CGSize)contentSizeForViewInPopover {
// Currently no way to obtain the width dynamically before viewWillAppear.
CGFloat width = 200.0;
CGRect rect = [self.tableView rectForSection:[self.tableView numberOfSections] - 1];
CGFloat height = CGRectGetMaxY(rect);
return (CGSize){width, height};
}
Try the following code:
- (IBAction)setData:(id)sender
{
UIViewController *popoverContent=[[UIViewController alloc] init];
UITableView *tableView=[[UITableView alloc] initWithFrame:CGRectMake(265, 680, 0, 0) style:UITableViewStylePlain];
UIView *popoverView=[[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 300)];
popoverView.backgroundColor=[UIColor whiteColor];
popoverContent.view=popoverView;
popoverContent.contentSizeForViewInPopover=CGSizeMake(200, 420);//setting the size
popoverContent.view=tableView;
tableView.delegate=self;
tableView.dataSource=self;
self.popoverController=[[UIPopoverController alloc] initWithContentViewController:popoverContent];
[self.popoverController presentPopoverFromRect:CGRectMake(400, 675, 0, 0) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
}
It looks like you want to do it programmatically, but in the storyboard, before you hook up a view controller to a segue, under the attributes inspector there is a "popover, use explicit size option." Maybe you can set the size that would work the best for your App first and not worry about the size with using code. Hope this helps, let us know how it goes.
I have two windows, my main window "window" and "help window" all inside my App Delegate. In my main window its view is subclassed and I want to draw a rect inside it. My help window has a rect also but it has an NSTracker on it. What I want to do is draw my rect in my window subclass with the x and y coordinates equal to my NSTracker position. The problem I am having is it crashes when I try to bring in the coordinates, any ideas of what I could be doing wrong? thanks
//My subclass of window is called CutoutView. This is all in draw rect
AppDelegate *controller = [[[NSApp delegate] window] contentView];
xValue = controller.mouseLoc.x;
yValue = controller.mouseLoc.y;
NSRectFillUsingOperation(NSMakeRect(xValue,yValue, 600, 400), NSCompositeClear);
[self update];
- (void)update
{
NSLog(#"test");
[self setNeedsDisplay:YES];
}
//My AppDelegate with the tracker helpView is a reference to the view of my second window "Help Window"
-(void)updateTrackingAreas
{
if(trackingArea != nil) {
[self.helpView removeTrackingArea:trackingArea];
[trackingArea release];
}
opts = (NSTrackingActiveAlways | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved);
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self.helpView bounds]
options:opts
owner:self
userInfo:nil];
[self.helpView addTrackingArea:trackingArea];
}
-(void)mouseMoved:(NSEvent *)theEvent
{
mouseLoc = [NSEvent mouseLocation];
NSLog(#"mouseMoved: %f %f", mouseLoc.x, mouseLoc.y);
}
in my CutoutView am i getting the AppController wrong because it is in a different window "helpWindow"? or does it have to do with my int values?
There are many things wrong with your code and it's obvious that you are fundamentally misunderstanding some basic concepts.
Firstly, you state that this code is in your drawRect: method;
AppDelegate *controller = [[[NSApp delegate] window] contentView];
xValue = controller.mouseLoc.x;
yValue = controller.mouseLoc.y;
NSRectFillUsingOperation(NSMakeRect(xValue,yValue, 600, 400), NSCompositeClear);
[self update];
There are several immediate flaws apparent. Firstly, why are you declaring controller to be of type AppController* when the method you are calling (-contentView) returns an NSView?
Your AppController is not a view (at least it should not be!), so you should be declaring the object as such:
NSView* mainView = [[[NSApp delegate] window] contentView];
If you are indeed using a view as a controller then this is completely wrong. See below for my note about MVC.
You don't specify where the mouseLoc property is coming from. We need to see where this is declared, because that will affect whether or not there are problems with it.
Your drawing code calls [self update], which simply tells the view to redraw itself. This will result in an infinite loop because every time the view draws it is forced to redraw. You should never call setNeedsDisplay: from inside drawRect:.
Even after making these changes, this code is very badly structured and the design is broken.
As it stands, your code violates the Model-View-Controller pattern. A views should not have knowledge of other views. You need to restructure things so that your views display properties of your controller without needing knowledge of other views. That means that you must store the mouse location in your controller (or a model object) and use some method for the view to access that information, preferably a datasource protocol or similar. In my answer to this other question I give an example of how to do that.
You need to read the Cocoa Drawing Guide. You also need to learn more core Cocoa concepts as it is clear you are misunderstanding how Cocoa is supposed to work.
I want to add some static text to a UITableViewCell in a UITextView.
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setBackgroundColor:[UIColor clearColor]];
[addressField setFont:[UIFont fontWithName:#"HelveticaNeue" size:14]];
[addressField setContentInset:UIEdgeInsetsMake(0, 20, 0, 0)];
[addressField setEditable:NO];
[addressField setScrollEnabled:NO];
// change me later
[addressField setText:#"John Doe\n555 Some Street\nSan Francisco, CA, 00000"];
[cell.contentView addSubview:addressField];
[addressField release];
This works great but I this code makes the cell unselectable probably because the UITextView is covering the entire cell.
How can I work around this so that I can have both the UITextView and selectable cells?
btw, I could make the UITextView size a bit smaller but users would still not be able to select the cell if they touch the UITextView.
I think a slightly better way to do it is to create a tap gesture recognizer on the entire table. (For example in your viewDidLoad)
// gesture recognizer to make the entire cell a touch target
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(changeFocus:)];
[tableView addGestureRecognizer:tap];
[tap release];
Then you create a selector (changeFocus: in this case) to do the actual selecting.
- (void)changeFocus:(UITapGestureRecognizer *)tap
{
if (tap.state == UIGestureRecognizerStateEnded)
{
CGPoint tapLocation = [tap locationInView:self.tableView];
NSIndexPath* path = [self.tableView indexPathForRowAtPoint:tapLocation];
[self tableView:self.tableView didSelectRowAtIndexPath:path];
}
}
You can make your changeFocus method more elaborate to prevent selections or give focus to specific subviews of the selected indexPath.
I would adopt the following approach in order to keep interaction enabled with both the UITextView and the UITableViewCell.
Declare your controller class (a UITableViewController I guess ?) as UITexView delegate.
When you declare your UITextView, set the table view controller as it's delegate.
Implement one of the UITextViewDelegate methods (ex : - (void)textViewDidChangeSelection:(UITextView *)textView) in your table view controller .m file.
From within this method you can manipulate the targeted cell either with a custom code or by triggering the tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *) delegate method through selectRowAtIndexPath:animated:scrollPosition:.
Your code might then look like :
In the table view controller .h file :
#interface MyTableViewController : UITableViewController <UITextViewDelegate> { ...
...
}
In the table view controller .m file :
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setDelegate:self];
...
Then implement this function for example (or any other suitable UITextViewDelegate function) :
- (void)textViewDidChangeSelection:(UITextView *)textView {
// Determine which text view triggered this method in order to target the right cell
...
// You should have obtained an indexPath here
...
// Call the following function to trigger the row selection table view delegate method
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]
}
Note that there are other alternatives like subclassing UITextView and deal with it's touch methods. I would recommend to use the possibilites offered by its delegate protocol though.
Note also that it might be handy to have your UITextView declared or at least referenced as an instance variable of the table view controller class. This will help you easily keep track of which addressField was hit and get the right indexPath.
[addressField setUserInteractionEnabled:NO];
I hope this helps you a bit:
[self.view insertSubview:TextView aboveSubview:TableView];
Or vice-versa based on your requirements.