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.
Related
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].
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];
}
I have a UIView class which I am currently removing from my view by using from inside the class [self removeFromSuperview]. Hopefully that's the correct thing to do.
However, now from my view controller (of which I add this view to) I need to know when it has removed itself so that I can call a method when this happens.
Generally speaking, the view shouldn't be doing things like removing itself. That's the job of the view controller.
If a UIView subclass can produce events that require the view hierarchy to be changed, I would define a delegate property for that view, and when an event occurs, call a method on that delegate. Then, when your view controller adds the view, it would set itself as the delegate and define the relevant method to handle the event.
If you have removed the UIView
view.removeFromSuperview()
you can check if it exists with the following code:
if !(view.superview != nil) {
//no superview means not in the view hierarchy
}
You could have a delegate callback setting the controller as the view's delegate. When you're about to remove the view, make the delegate callback and implement the callback method in your controller.
The 'removeFromSuperview' has always seemed backwards to me… :(
I'm assuming you are making the remove call after some sort of action, like a button press or something. if that is the case, set the buttons delegate to be the view controller, not the view class, and inside the action method in the view controller, call
[yourCustomView removeFromSuperview];
The best choice would be to let the controller remove the view
[self.view removeFromSuperview];
and to know if the view was removed (or never added) you can ask
if(![self.view superview]) {
//no superview means not in the view hierarchy
}
Not sure what sdk you are using - but I am using iOS 5 and I just use the following method in the superview:
-(void)willRemoveSubview:(UIView *)subview{
if([subview isEqual:someView]){
//do stuff
}
//you could do some stuff here too
}
Sorry I previously posted a question here, but The answers I got didn't work (probably my fault).
Basically, I want to stop a UIPopover from dismissing automatically. I got to this piece of code:
- (BOOL) popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
return NO;
}
which I put in, but it doesn't seem to have any effect. Is there anything else I should add?
am I putting it in the right place (I was putting it in the vc.m of the view that has the popover within it)?
Thanks Very Much,
Luke
Check to make sure the UIPopover delegate is set. You can do this in code by setting the popover instance variable:
aPopover.delegate = self;
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