NSView drawRect method not working? - objective-c

Right now I have two windows. One of them has an NSView, which acts as a background color and the other one has a colorwell that changes a color variable in a shared instance.
My program sort of works. I can open my colorwell window and select a color, but the background color only updates if I manually resize the window.
I tried to get past this issue by having a thread in the background looping:
[self setNeedsDisplay:YES];
[self updateLayer];
[self display];
I'm not completely sure that those 3 lines are necessary, but I am sure that they are re-calling my drawRect method.
I even threw an NSLog into the drawRect method to test. I saw in the console that it was getting called over-and-over again.
Here is my drawRect method:
- (void)drawRect:(NSRect)dirtyRect{
[theDATA.main_frame_background_color setFill];
[NSBezierPath fillRect:dirtyRect];
}
theDATA.main_frame_background_color is an NSColor from a shared instance. I am positive that the value is changing because my NSView updates when I resize the window.
I'm completely clueless on why this is not working. Hope you can help.

First, never call a display method directly. Mark dirty area with setNeedsDisplay: and the view will be redrawn after a while. If your view is layer-backed, be aware of layerContentsRedrawPolicy property, since it's default value is NSViewLayerContentsRedrawNever. There's no way to automatically determine which policy fit your needs best, so this property should be set manually.

I solved my problem thanks to #Sega-Zero's suggestion of using layerContentsRedrawPolicy and through a bit of research I found out that I couldn't directly call setNeedsDisplay in a thread, so I used performSelectorOnMainThread.
My Thread class:
- (void)updateLOOP{
while (true){
[self performSelectorOnMainThread: #selector(refreshClock)
withObject: nil
waitUntilDone: NO];
[NSThread sleepForTimeInterval:.1];
}
}
}
External update method:
- (void) refreshClock{
[self setNeedsDisplay:YES];
}
Now everything works as it should. The drawRect method is getting called without having to use [self display].

Related

CALayer setNeedsDisplay can not work?

My class is a CALayer subclass, and I have added a observer through NSNotificationCenter, when catching the message, it will run function like this:
- (void)setCurrentAutoValue:(NSNotification *) obj
{
[self setNeedsDiaplay];
}
but this does not work, can anyone help?
Apparently, it is necessary to call setNeedsDisplay on the main thread. You can do this by using the performSelectorOnMainThread method. For example in your case:
[self performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:0 waitUntilDone:NO];
I had the same issue updating my CALayers through another thread. If i called any of the following lines directly in the same thread it worked perfectly:
[self setNeedsDisplay:YES];
[self needsDisplay];
[self.circleLayer needsDisplay];
[self.circleLayer setNeedsDisplay];
But when calling these through another thread, there was no change. I played around a bit and ended up using
[self display];
which was the only thing that forced the CALayers to update. Self is an NSOpenGLView which contains several layers and sublayers. Maybe this can help. Every CALayer has the same method available. Maybe you should call display.
My problem was that i could only update a CALayer with an animation, not by setting it's content (a CGPath to the property "path"). What seems to be the only solution was removing all animations of the layer first and then set the new path:
[_myLayer removeAllAnimations];
UIBezierPath *newPath = ...
_myLayer.path = newPath.CGPath;
Note to self: Or the frame of the CALayer was never set. Whenever there's a problem with a CALayer, first makes sure its frame != NSZeroRect.

Redrawing a view in ios

Is it possible to redraw the whole view.
I need it to complete my language settings. The problem is that the language only changes after the views there drawn again. Like you go out of settings and then go in again, then the language is changed. But the moment you save everything, the language stays the same.
So how should i redraw my views, or by best the whole app, after the language change was found?
In ARC:
- (void)setLanguage:(LanguageType)languageType
{
_language = languageType;
//TODO:your settings
[_theWholeView setNeedsDisplay];
}
If that can not work , there is a very troublesome solution , it can reload the whole view.
You can code like this:
In the view.h , you need creat a delegate.
#protocol WholeViewDelegate
- (void)reloadData;
#end
In the view.m
- (void)setLanguage:(LanguageType)languageType
{
_language = languageType;
//TODO:your settings
[_delegate reloadData];
}
In the controller you need to implement the delegate
In the controller.m
- (void)reloadData
{
if(_wholeView)
{
[_wholeView removeFromSuperview];
}
// in the wholeView init method you should refresh your data
_wholeView = [[WholeView alloc] init];
self.view addSubview:_wholeView
}
Yes. Call [UIView setNeedsDisplay] (reference).
Just call setNeedsDisplay.. It will resolve the problem. setNeedsDisplay actually calls the drawRect function in the UIView class by passing the frame of the view as the rectangular parameter. Hope that helps....

NSTextField set cursor

Okay so I feel like there's something obvious I'm missing in this question. I've used makeFirstResponder throughout my code to move from textField 1 to 2, 2 to 3, etc. That seems to work as I want it to, yet when the new view is loaded, I want the cursor to be in textField1, and yet the following code does not place the cursor in textField1 upon load.
- (void) awakeFromNib{
[[[self view] window] makeFirstResponder:textField1];
}
I also tried setInitialFirstResponder, and that didn't have any effect either (I don't even think that would be right.) So, is it because it is in the awakeFromNib method? Can anyone tell me what I'm missing? Thanks in advance.
EDIT - My solution was differed slightly from the accepted answer so I thought I'd post my implementation. Because the view I wanted to set the first responder for was a subview added later (think the second screen of an application wizard), I simply added a setCursorToFirstTextField method:
- (void) setCursorToFirstTextField {
[[[self view] window] makeFirstResponder:textField1];
}
And made sure to call it after I had added the subview to the custom view on the original window.
Yes, you're right about the problem being the location of the method in awakeFromNib. If you log [self.view window] in your awakeFromNib, you'll see that it's NULL. I don't know how exactly you have things set up, but I'm guessing (if this relates to your WizardController question) that you're doing an alloc initWithNibName:bundle: in another class to create your view controller and then adding that controller's view to the view hierarchy. If you throw some logs in there, it will show you that awakeFromNib in the controller class is called after the alloc init, but before the view is added as a subview, so there is no window at that time. The way I got around this problem was to create a setup method in the view controller class (with the makeFirstResponder code in it), and call it from the class where you create the controller after you add it as a subview.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.wizard = [[WizardController alloc] initWithNibName:#"WizardController" bundle:nil];
[self.window.contentView addSubview:wizard.view];
[self.wizard doSetup];
}

What is the most robust way to force a UIView to redraw?

I have a UITableView with a list of items. Selecting an item pushes a viewController that then proceeds to do the following. from method viewDidLoad I fire off a URLRequest for data that is required by on of my subviews - a UIView subclass with drawRect overridden. When the data arrives from the cloud I start building my view hierarchy. the subclass in question gets passed the data and it's drawRect method now has everything it needs to render.
But.
Because I don't call drawRect explicitly - Cocoa-Touch handles that - I have no way of informing Cocoa-Touch that I really, really want this UIView subclass to render. When? Now would be good!
I've tried [myView setNeedsDisplay]. This kinda works sometimes. Very spotty.
I've be wrestling with this for hours and hours. Could someone who please provide me with a rock solid, guaranteed approach to forcing a UIView re-render.
Here is the snippet of code that feeds data to the view:
// Create the subview
self.chromosomeBlockView = [[[ChromosomeBlockView alloc] initWithFrame:frame] autorelease];
// Set some properties
self.chromosomeBlockView.sequenceString = self.sequenceString;
self.chromosomeBlockView.nucleotideBases = self.nucleotideLettersDictionary;
// Insert the view in the view hierarchy
[self.containerView addSubview:self.chromosomeBlockView];
[self.containerView bringSubviewToFront:self.chromosomeBlockView];
// A vain attempt to convince Cocoa-Touch that this view is worthy of being displayed ;-)
[self.chromosomeBlockView setNeedsDisplay];
Cheers,
Doug
The guaranteed, rock solid way to force a UIView to re-render is [myView setNeedsDisplay]. If you're having trouble with that, you're likely running into one of these issues:
You're calling it before you actually have the data, or your -drawRect: is over-caching something.
You're expecting the view to draw at the moment you call this method. There is intentionally no way to demand "draw right now this very second" using the Cocoa drawing system. That would disrupt the entire view compositing system, trash performance and likely create all kinds of artifacting. There are only ways to say "this needs to be drawn in the next draw cycle."
If what you need is "some logic, draw, some more logic," then you need to put the "some more logic" in a separate method and invoke it using -performSelector:withObject:afterDelay: with a delay of 0. That will put "some more logic" after the next draw cycle. See this question for an example of that kind of code, and a case where it might be needed (though it's usually best to look for other solutions if possible since it complicates the code).
If you don't think things are getting drawn, put a breakpoint in -drawRect: and see when you're getting called. If you're calling -setNeedsDisplay, but -drawRect: isn't getting called in the next event loop, then dig into your view hierarchy and make sure you're not trying to outsmart is somewhere. Over-cleverness is the #1 cause of bad drawing in my experience. When you think you know best how to trick the system into doing what you want, you usually get it doing exactly what you don't want.
I had a problem with a big delay between calling setNeedsDisplay and drawRect: (5 seconds). It turned out I called setNeedsDisplay in a different thread than the main thread. After moving this call to the main thread the delay went away.
Hope this is of some help.
The money-back guaranteed, reinforced-concrete-solid way to force a view to draw synchronously (before returning to the calling code) is to configure the CALayer's interactions with your UIView subclass.
In your UIView subclass, create a displayNow() method that tells the layer to “set course for display” then to “make it so”:
Swift
/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
public func displayNow()
{
self.layer.setNeedsDisplay()
self.layer.displayIfNeeded()
}
Objective-C
/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
- (void)displayNow
{
[self.layer setNeedsDisplay];
[self.layer displayIfNeeded];
}
Also implement a draw(_: CALayer, in: CGContext) method that'll call your private/internal drawing method (which works since every UIView is a CALayerDelegate):
Swift
/// Called by our CALayer when it wants us to draw
/// (in compliance with the CALayerDelegate protocol).
override func draw(_ layer: CALayer, in context: CGContext)
{
UIGraphicsPushContext(context)
internalDraw(self.bounds)
UIGraphicsPopContext()
}
Objective-C
/// Called by our CALayer when it wants us to draw
/// (in compliance with the CALayerDelegate protocol).
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
UIGraphicsPushContext(context);
[self internalDrawWithRect:self.bounds];
UIGraphicsPopContext();
}
And create your custom internalDraw(_: CGRect) method, along with fail-safe draw(_: CGRect):
Swift
/// Internal drawing method; naming's up to you.
func internalDraw(_ rect: CGRect)
{
// #FILLIN: Custom drawing code goes here.
// (Use `UIGraphicsGetCurrentContext()` where necessary.)
}
/// For compatibility, if something besides our display method asks for draw.
override func draw(_ rect: CGRect) {
internalDraw(rect)
}
Objective-C
/// Internal drawing method; naming's up to you.
- (void)internalDrawWithRect:(CGRect)rect
{
// #FILLIN: Custom drawing code goes here.
// (Use `UIGraphicsGetCurrentContext()` where necessary.)
}
/// For compatibility, if something besides our display method asks for draw.
- (void)drawRect:(CGRect)rect {
[self internalDrawWithRect:rect];
}
And now just call myView.displayNow() whenever you really-really need it to draw (such as from a CADisplayLink callback).  Our displayNow() method will tell the CALayer to displayIfNeeded(), which will synchronously call back into our draw(_: CALayer, in: CGContext) and do the drawing in internalDraw(_: CGRect), updating the visual with what's drawn into the context before moving on.
This approach is similar to #RobNapier's above, but has the advantage of calling displayIfNeeded() in addition to setNeedsDisplay(), which makes it synchronous.
This is possible because CALayers expose more drawing functionality than UIViews do— layers are lower-level than views and designed explicitly for the purpose of highly-configurable drawing within the layout, and (like many things in Cocoa) are designed to be used flexibly (as a parent class, or as a delegator, or as a bridge to other drawing systems, or just on their own). Proper usage of the CALayerDelegate protocol makes all this possible.
More information about the configurability of CALayers can be found in the Setting Up Layer Objects section of the Core Animation Programming Guide.
I had the same problem, and all the solutions from SO or Google didn't work for me. Usually, setNeedsDisplay does work, but when it doesn't...
I've tried calling setNeedsDisplay of the view just every possible way from every possible threads and stuff - still no success. We know, as Rob said, that
"this needs to be drawn in the next draw cycle."
But for some reason it wouldn't draw this time. And the only solution I've found is calling it manually after some time, to let anything that blocks the draw pass away, like this:
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(0.005 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[viewToRefresh setNeedsDisplay];
});
It's a good solution if you don't need the view to redraw really often. Otherwise, if you're doing some moving (action) stuff, there is usually no problems with just calling setNeedsDisplay.
I hope it will help someone who is lost there, like I was.
You can use CATransaction to force a redraw:
[CATransaction begin];
[someView.layer displayIfNeeded];
[CATransaction flush];
[CATransaction commit];
Well I know this might be a big change or even not suitable for your project, but did you consider not performing the push until you already have the data? That way you only need to draw the view once and the user experience will also be better - the push will move in already loaded.
The way you do this is in the UITableView didSelectRowAtIndexPath you asynchronously ask for the data. Once you receive the response, you manually perform the segue and pass the data to your viewController in prepareForSegue.
Meanwhile you may want to show some activity indicator, for simple loading indicator check https://github.com/jdg/MBProgressHUD

setNeedsDisplay not working?

I have a problem redrawing a custom view in simple cocoa application. Drawing is based on one parameter that is being changed by a simple NSSlider. However, although i implement -setParameter: and -parameter methods and bind slider's value to that parameter in interface builder i cannot seem to make a custom view to redraw itself.
The code that does redrawing is like this:
- (void)setParameter:(int)newParameter {
parameter = newParamter;
NSLog(#"Updated parameter: %d", parameter);
[self setNeedsDisplay:YES];
}
I DO get the message about setting the new parameter although the view doesn't redraw itself. Any ideas are welcome!
The usual syntax is: [self setNeedsDisplay:YES], although I would assume that that means the same thing. Are you implementing the - (void)drawRect:(NSRect)rect method, or using the drawRect: method of your superclass?
For anyone that has this problem while using an NSOpenGLView subclass, you might be forgetting to use [[self openGLContext] flushBuffer] at the end of drawRect:.
Sometimes reason can be very simple: File's owner has no connection to UIView object. i.e. it's Outlet is not setup properly.
Use IB, ctrl button and drag method :)
In the iOS 6 there isn't such function to call: setNeedsDisplay:YES. I've got the same problem, and came with this solution: https://stackoverflow.com/a/15027374/1280800 .
Hope it will help.