I have the need to draw a background image or set a tint color on a navigation bar, but I also need the option to have the navigation bar appear as it normally would. I'm currently using a category to support If my app does not specify a background image, what can I do instead to ensure the drawRect method does it normally would do?
I.E. -
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
if(hasImage){
UIImage *img = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://myimageurl.com/img.jpg"]]];
[img drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}else{
??????
}
}
#end
I actually ended up doing something entirely different and I'm wondering why nobody hasn't discovered this before. One of the approaches I've seen in the course of my Googling on the subject was simply adding an image as a subview to the UINavigationBar. The only problem was this made the buttons in the bar not clickable. The fix was to disable user interaction on the image.
myUIImageView.userInteractionEnabled = NO;
[myNavController.navigationBar addSubview:myUIImageView];
[myNavController.navigationBar sendSubviewToBack:myUIImageView];
With that, everything looks/works great and I don't have to override the drawRect method with a category, swizzle methods or any of that funky stuff. Simple and clean.
Theoretically you could do this by subclassing UINavigationBar overriding only the drawRect: method, and then calling [super drawRect:rect] when you want to use the default behavior.
But I don't believe you can in practice because you don't instantiate the UINavigationBar directly.
Solving this is possible but nontrivial, since the category method "replaces" the original method rather than subclassing it from the runtime's standpoint. This is why just using, say, super, won't work.
You should check out this post on "supersequent" implementation: http://cocoawithlove.com/2008/03/supersequent-implementation.html
(That link and some other related ideas are in the answer to this question: Using Super in an Objective C Category? )
Related
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.
I want to change background color for many nsview. I override drawRect: on subclass NSview but i don't know how to set background color for myview( is reference IBOUTLET). please help me. Thanks so much
Code for CustomView.h
#import <Cocoa/Cocoa.h>
#interface CustomView : NSView
#end
Code for CustomView.m
#import "CustomView.h"
#implementation CustomView
- (void) drawRect:(NSRect)dirtyRect {
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
#end
And in main class, i added #import "CustomView.h" but i don't know how to set background for myview.
Welcome to Cocoa drawing.
Cocoa drawing uses Quartz which is a PDF model.
Drawing in this occurs in a back to front procedural order.
In Quartz drawing there is a drawing environment state object called the Graphics Context.
This is an implicit object in many of the drawing ops in AppKit.
(in Core Graphics or other APIs it could need to be explicitly called)
You tell the Graphics Context what the current color and other parameters are, then draw something, then change parameters and draw more, etc...
In AppKit, you do this by sending a message to the NSColor object, which is weird. but that's how it works.
In your drawRect: method you should call super first usually, because you probably want your drawing on top of that...
- (void) drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// This next line sets the the current fill color parameter of the Graphics Context
[[NSColor whiteColor] setFill];
// This next function fills a rect the same as dirtyRect with the current fill color of the Graphics Context.
NSRectFill(dirtyRect);
// You might want to use _bounds or self.bounds if you want to be sure to fill the entire bounds rect of the view.
}
If you want to change the color, you'll need an #property NSColor
You might need more than one for your drawing.
That allows you to set the color.
You might want the view to use KVO and observe its own color property then draw itself if the color property changes.
You could do a lot of different things to set the color. (a button or pallette elsewhere) But all of them would eventually result in sending a message to set the color of a property of your view for drawing.
Finally, if you want to update the drawing, you need to call [myView setNeedsDisplay:YES]; where myView is a reference to an instance of the NSView subclass.
There is also display but that's forceful.
setNeedsDisplay: says to schedule it on the next run of the event loop (runLoop). display kind of makes everything jump to that right away.
The event loop comes back around fast enough you shouldn't force it.
Of note, setNeedsDisplay: is the entire view.
In a fancy ideal world with complex views, you might want to more appropriately optimize things by calling setNeedsDisplayInRect: where you designate a specific CG/NSRect of the view as needing to be redrawn.
This allows the system to focus redrawing to the smallest union rect possible in the window.
I'm super late, but this is how I do it - there's no need to sub class:
NSView *myview = [NSView new];
[view setWantsLayer:YES];
view.layer.backgroundColor = [NSColor greenColor].CGColor;
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.
I am looking for a perfect solution to set a background image for a window in a cocoa application. I haven't found a solution to this, I am new in objective c, so please anyone help me...
A window in Cocoa has a root-level view called the "content view". This is the view that contains all the others in a window. By default, it's just a plain, blank NSView. But you could easily create your own custom NSView subclass, override the drawRect: method to draw your background image, and use that for your custom view.
However, it might just be easier to use a plain old NSImageView. The advantage of this is that you can set, for example, autosizing behavior to keep the image pinned to one corner (try this with Installer.app by resizing the installer window). You would also be able to make it semi-opaque so that the background shows through a bit. (Again, I'm thinking of Installer.app; your app could be totally different)
Hope that gets you going in the right direction!
Michael Vannorsdel suggests sublassing NSView for the purpose, and I quote:
You'd really be better off making an
NSView subclass and having it draw
the image you want in drawRect:.
- (void)awakeFromNib
{
myImage = [[NSImage alloc] init....
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)rect
{
NSSize isize = [myImage size];
[myImage drawInRect:[self bounds] fromRect:NSMakeRect(0.0, 0.0,
isize.width, isize.height) operation: NSCompositeCopy fraction:1.0];
}
Read that whole thread on cocoabuilder, it's quite instructive.
So, I have a custom UIView subclass which enables drawing of rounded edges. The thing draws perfectly, however the background always fills the whole bounds, despite clipping to a path first. The border also draws above the rectangular background, despite the fact that I draw the border in drawRect: before the background. So I removed the whole content of drawRect:, which is now virtually empty - nevertheless the background gets drawn!
Anybody an explanation for this? I set the backgroundColor in Interface Builder. Thanks!
Sorry for this monologue. :)
The thing is that the UIView's layer apparently draws the background, which is independent from drawRect:. This is why you can't get rid of the background by overriding drawRect:.
You can either override - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx and make the layer draw whatever you want, or you override - (void) setBackgroundColor:(UIColor *)newColor and don't assign newColor to backgroundColor, but to your own ivar, like myBackgroundColor. You can then use myBackgroundColor in drawRect; to draw the background however you like.
Overriding setBackgroundColor:
Define an instance variable to hold your background color, e.g. myBackgroundColor. In your init methods, set the real background color to be the clearColor:
- (id) initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super init...])) {
[super setBackgroundColor:[UIColor clearColor]];
}
return self;
}
Override:
- (void) setBackgroundColor:(UIColor *)newColor
{
if (newColor != myBackgroundColor) {
[myBackgroundColor release];
myBackgroundColor = [newColor retain];
}
}
Then use myBackgroundColor in your drawRect: method. This way you can use the color assigned from Interface Builder (or Xcode4) in your code.
Here's an easier fix:
self.backgroundColor = [UIColor clearColor];
I hesitate to suggest this because I don't want to offend you, but have you set opaque to NO?
This is easily reproducible:
Create a new View-Based iPhone Application. Create an UIView subclass and leave the drawRect: method completely empty. In Interface Builder, drag a UIView into the main view, assign it a background color and set the class of the view to your subclass. Save, build and run, and see, the view shows the background color.
I have circumvented this behaviour by overriding setBackgroundColor: and assigning the color to my own ivar, always leaving the backgroundColor property nil. This works, however I'm still wondering why this works this way.
You should set
clearsContextBeforeDrawing = NO
It's that simple.
From the UIView Reference Docs
Oops. I misunderstood the question. The OP (original poster) wants their custom control to support the standard "backgroundColor" property (eg, set by the gui designer), but does NOT want the system to paint that color. The OP wants to paint the bg himself so that he can round the corners.
In that case, the post about overriding the layer level drawing is correct.
The solution I posted will prevent the UI system from clearing your buffer before drawing. When you have no bg defined, if clearsContextBeforeDrawing is set, the iOS will clear your view's buffer, setting all pixels to transparent black. Doing this for a full-screen view takes about 5ms in an iPad3, so it's not free (pushing that 2048x1536 pixels never is). For comparison drawing a full-screen bitmap (using kCGBlendModeCopy to force blitting) takes ~25ms (using quartz, not GPU).
While it is possible to override the code that copies the background colour into the view's layer, I'm not a fan of this approach. I believe it can cause issues with views intended to be opaque. My suggestion is to create a custom colour variable like this:
#IBInspectable var foregroundColor: UIColor = .black {
didSet { setNeedsDisplay() }
}
Now, set your view's backgroundColor to clear and use foregroundColor in your drawing code in its place. The code above takes care of updating it when the value changes, and also exposes it to Interface Builder if required.
Another alternative is to use your view's tintColor instead, and to ensure that it updates correctly when tint colour changes. You may be reserving tint colour for other uses though, so this is not necessarily ideal. If you use tintColor, don't forget to include the following:
override func tintColorDidChange() {
setNeedsDisplay()
}