-setNeedsDisplay not getting called to refresh drawRect - objective-c

I have two methods that get called from within -drawRect:
- (void)drawRect:(CGRect)rect {
if(drawScheduleFlag) {
[self drawSchedule];
drawScheduleFlag = false;
}
else
[self drawGrid];
}
-drawGrid is called at initialization time. The other method (-drawSchedule) is called from this code, which is on the main thread:
- (void) calendarTouched: (CFGregorianDate) selectedDate {
// NSLog(#"calendarTouched - currentSelectDate: %d/%d/%d", selectedDate.month, selectedDate.day, selectedDate.year);
// NSLog(#"Main thread? %d", [NSThread isMainThread]);
// get data from d/b for this date (date, staff name, cust name, time, length, services required)
//------ stub -------
scheduledDate.year = 2012;
scheduledDate.month = 6;
scheduledDate.day = 20;
staffName = #"Saori";
custName = #"Brian";
startTime.hour = 11;
duration = 2;
servicesReqd = #"Nails";
drawScheduleFlag = true;
[self setNeedsDisplay];
return;
}
I know the code is being executed, but nothing happens to draw the schedule. Why is -[self setNeedsDisplay] not causing the -drawRect to be called?
UPDATE: I have put breaks in so I'm positive it's not being called. The original grid is drawn once; when the user taps a calendar date, -calendarTouched is called and completely executed. The drawScheduleFlag is set to true, and -[self setNeedsDisplay] gets called, but -drawRect does not. It appears that the UIView is not being invalidated (which -setNeedsDisplay is supposed to do), therefore -drawRect is not called.
UPDATE #2: I have a .xib for a UIViewController with two (2) UIViews in it. The first UIView takes about 1/3 of an iPad screen, the second UIView takes the bottom 2/3 of the screen. Each UIView has it's own specific class; the top UIView displays a calendar and is working correctly, capturing touches and changing the date selected.
The bottom UIView is supposed to show the schedule for the date picked in the top UIView. This is where the problem is. Since the top UIView is working, I will put up the code for the bottom UIView's class. It basically draws the schedule grid when -drawRect is first called. Once the user has selected a day, it is supposed to invalidate the UIView to draw the actual schedule on the grid.
Here is the code for Schedule.h: http://pastebin.com/NQpj0i07
Here is the code for Schedule.m: http://pastebin.com/YBbE8y0T
Here is the code for the controller: http://pastebin.com/nDqBCivj
Note that both pastebin's expire in 24 hours from Jun 28, 5:38 PM PST

Repeating what I said in the comments, the problem you are having is that your UIView subclass object is getting deallocated before the drawRect call gets executed, which means that whatever is holding the reference to the object, in this case your controller, is releasing it before you mean to, little issue that sometimes comes up with using ARC.
In the controller code that holds this view you should have something like this declared:
#property (weak, nonatomic) IBOutlet Schedule *scheduleView;
And synthesized:
#synthesize scheduleView = _scheduleView;
And in the controller's dealloc method:
self.scheduleView = nil;
If you're using storyboards, which you appear to not be using since you made a .xib file for this, you should have it hooked up properly. If not simply instantiate it and assign it.
Expanding on what I said before, I'm not sure what you're trying to do but taking a look at your code you are creating the view for a brief moment when the notification gets called and right there it's getting deallocated because no one is holding a reference to it. By doing what I said and control+click drag from the controller to the view in interface builder and hooking it up it will hold the reference to it, and you'll only have 1 Schedule object created.
After that you'll have to modify your code to work with this instance of Schedule:
- (void) testNotification:(NSNotification *) notification {
// was the calendar tapped?
if ([[notification name] isEqualToString:#"calendarUpdated"]) {
NSDictionary *passedData = notification.userInfo; // get passed data
CFGregorianDate currentSelectDate;
NSData *data = [passedData objectForKey:#"currentSelectDate"];
[data getBytes:&currentSelectDate length:sizeof(CFGregorianDate)];
[self.scheduleView calendarTouched:currentSelectDate];
}
return;
}
The other option you have if you're not using interface builder to set everything up is the following in your viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.scheduleView = [[Schedule alloc] init];
[self.view addSubView:self.scheduleView]; // Probably some extra code for positioning it where you want
// notify me when calendar has been tapped and CFGregorianDate has been updated
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(testNotification:) name:#"calendarUpdated" object:nil ];
}
As a final note, considering you're new to iOS development (as we all were) you can go to iTunes and look for Stanford's iPad and iPhone Development course (CS193P) on iTunes U, it's completely free and it will teach you most of what you need to know for developing in iOS.

Related

no methods on NSWindowController subclass are called

I have a window that I am creating based off of clicking a row in a Tableview, instantiated like such:
HKLUserProfileController *userProfileController = [[HKLUserProfileController alloc] initWithNibName:#"HKLUserProfileController" bundle:nil];
_wc =[[NSWindowController alloc] initWithWindowNibName:#"HKLProfileWindowController"];
[_wc.window.contentView addSubview:userProfileController.view];
// other extraneous stuff here
[_wc showWindow:self];
if([_wc.window canBecomeKeyWindow]) {
[_wc.window makeMainWindow];
[NSApp activateIgnoringOtherApps:YES];
[self makeKeyAndOrderFront:self];
}
This works, but I cannot seem to get the window to become the main/key/front window. I've tried:
... from this place where I am creating the WindowController, and also from inside viewDidLoad/loadView of the NSViewController whose View was added to the Window. No dice. (this is my ultimate goal here, so if you see something obvious i'm missing, please point it out).
So I realized that I should be trying to do this in the subclass of the WindowController itself... so I thought to put it in windowDidLoad, but no result. I set some breakpoints, and tried the other logical init methods, and to my surprise, NONE of them fire at all.
#implementation HKLProfileWindowController
- (void)windowDidLoad {
[super windowDidLoad];
// breakpoint here
}
-(void)awakeFromNib {
// breakpoint here
}
- (id)init {
// breakpoint here
self = [super init];
if (self) {
// breakpoint here
}
return self;
}
#end
My xib is connected as an Outlet / Delegate to my File's Owner properly, as far as I can tell. This is leading me to believe this is the root of the problem, but for the life of me, I can't figure out what is the issue...
Thanks...
EDIT
I realize what is happening with the keyWindow - because it's coming from shouldSelectRow in the TableView, it IS becoming key on mouseDown events, but the first window - which holds the TableView - is becoming key again on mouseUp events... which is maddening!
Will search for solutions to that, but open to comments here!
(still can't figure out why the init methods are not firing though...
You use NSWindowController in your alloc/init line and you expect it to become an HKLProfileWindowController instance? Why?
You also need the File's Owner custom class set to HKLProfileWindowController but there's no reason to expect that's sufficient to get an HKLProfileWindowController instance when you ask for an NSWindowController. What you get is an NSWindowController and none of the subclass code is used.
I'll delete this answer if your code is from before you implemented the HKLProfileWindowController class, but given what you have pasted here, that's why your breakpoints don't get hit.

UIViewcontroller not reloading after notification from observer

Hi I have a uiviewcontroller that adds 4 childviews after receiving data from a webservice. I add an observer to the uiviewcontroller that watches when the data is completely loaded from the webservice. Once it is, the uiviewcontroler adds 4 childviews.
however my problem is it doesn't do this unless i switch to another tab view than go back to that tab again.
Is there a way to reload this page? I used [uiviewcontroller.view setNeedsDisplay] under the function that responds to the notification from the observer but the view doesn't refresh.
thanks for your guys' help :)
here is my observer function:
-(void)projectListFromServer:(NSNotification *) response
{
NSLog(#"%#",response);
for (int i = 0; i < 4; i++)
{
childview2 = [self.storyboard instantiateViewControllerWithIdentifier:(#"slider")];
childview2.image = #"wicked";
childview2.couponID = #"cool";
NSLog(#"%#", self.childview2.couponID);
[self addChildViewController:childview2];
}
[self.childview.view setNeedsDisplay];
}
You are calling addChildViewController, but then it looks like you aren't actually adding childview2's view hierarchy to this containing view controller's view hierarchy.
You'll need to do something like this, adjusted of course depending on where in your current hierarchy you actually want these new views to be inserted:
[self addChildViewController:self.childview2]; // assuming childview2 is a #property
[self.view addSubview:self.childview2.view]; // adjust based on your actual hierarchy
[self.childview2 didMoveToParentViewController:self];
BTW, you should avoid giving view controllers names like childview2. Views and view controllers are two completely different things.

Delegating Outline View's Data Source To Separate Object

I want to be able to use a blue object box to delegate control over an NSOutlineView. The blue object box would be hooked up to my primary controller, so it'd just be a data source and control the content of the NSOutlineView.
The problem I'm having is that I have no control over the Channel Data Source. I'm simply calling a declared method with some test NSLog inside of it, and it doesn't get called. The outlet doesn't get instantiated.
Here's the connections of the blue object box (ChannelDataSource)
Here's the connections of File's Owner for my primary controller.
So you see, I want to do something like [dataSource callMyMethod]; with the final aim that I have control over the contents for the NSOutlineView.
Any ideas?
EDIT
The application is structured whereby my primarily app delegate looks like this:
#implementation MyAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
controller = [[MainController alloc] init];
[controller showWindow];
}
#end
Then in the MainController I have something along the following lines:
#implementation MainController
-(id)init {
self = [super init];
if (self) {
// loads of random stuff
[dataSource myMethod];
}
return self;
}
So "Channel Data Source" blue object box is dataSource. At this point in the application life cycle, it's null, which isn't what I was expecting. At the same time, it's still a bit of black magic to me. If you have a blue object box, at what point is it instantiated? Obviously this isn't hooked up correctly though.
EDIT EDIT
Further to my points above, and trying to fix the problem, is this actually a good way to go about it? I'm looking at this thinking it's not meeting a decent MVC architecture, because ultimately the blue object box's owning class is storing and managing the data. Is there a better way to go about managing what's in your NSOutlineView?
EDIT EDIT EDIT
So I have my app delegate, which is strangely a class all by itself that instantiates the main controller. Don't ask me why I did this, it was very early code. So my app delegate (root entry point) has this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
controller = [[MyController alloc] initWithWindowNibName:#"MainWindow"];
[controller showWindow:nil]; // this doesn't open the window
[controller loadWindow]; // this does open the window
}
And the declaration of the controller:
#interface MyController : NSWindowController
Which contains the following method declaration in it:
-(void)windowDidLoad {
[dataSource insertChannel:#"test" forServer:#"test2"];
}
I have a breakpoint in windowDidLoad and it definitely doesn't get called.
Ideas?
There's still a few things you didn't clarify, but I can do some guessing. First, I'm assuming that MainController is a subclass of NSWindowController. If so, you should be using initWithWindowNibName: instead of just init, otherwise how would the controller know what window to show when you address showWindow: to it? Second, even if you do that, and change your init method to initWithWindowNibNamed:, what your wrote won't work, because the init is too early in the process to see your outlet, datasource. If you just log dataSource it will come up null. A better place to put that code would be in windowDidLoad, as everything will have been set up by then (this will be called after showWindow:). So, in my little test project, this is what I did.
In the app delegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.cont = [[Controller alloc] initWithWindowNibName:#"Window"];
[self.cont showWindow:nil];
}
In the Controller.M I have this:
- (void)windowDidLoad {
NSLog(#"%#",self.dataSource);
[self.dataSource testMethod];
}
In IB, in the Window.xib file, I set the class of the file's owner to Controller, and the class of the blue cube to ChannelDataSource. EVerything was hooked up the same way you showed in your post.

UIButton touches up IBAction causing EXC_BAD_ACCESS with ARC

There have been a few questions on StackOverflow where users have experienced the same problem as I am having. However, none of their solutions fit my case. (See here, here, here and here for some of the SO questions I've read but have not found helpful.)
In my case, I have a NIB that has a couple UIButtons, with an associated Controller View. The view is relatively old to my project and I've been able to use these buttons without any trouble until today. After making a few code changes that were not related to the button behavior, I've run into an error that crashes the app, breaks the code at the main() function and gives me an EXC_BAD_ACCESS error message whenever I touch any of the buttons on my View.
How or why might this happen? I've actually commented out almost all functional code, especially that which I modified earlier today and I still can't stop the error from occurring.
My project is using Automatic Reference Counting and I haven't seen this error before. Furthermore, I did not modify the NIB, nor the IBAction associated with the buttons so I don't see what would cause this. The only way to stop the error is to unlink my UIButtons in my NIB to the IBActionmethods defined in my Controller View header file.
The only "unique" aspect of my use-case is that I load either one or two instances of this view, within another sub view controller. The number of instances of the broken view that are loaded is contingent on the number of objects in an array. Below is the code that I use to instantiate and load these views as subviews of another view.
//Called else where, this starts the process by creating a view that
//will load the problematic view as a sub-view either once or twice.
- (id)initWithPrimarySystemView:(SystemViewController *)svc
{
//First we create our parent, container view.
self = [super initWithNibName:#"ContainerForViewInstaniatedFromArrayObjs" bundle:nil];
if (self)
{
//Assign parent DataModel to local instance
[self setDataModel:((DataModelClass*)svc.DataModel)];
for (AnotherModel* d in DataModel.ArrayOfAnotherModels)
{
//Instantiate the SubViewController.
SubViewController* subsvc = [[SubViewController alloc]
initWithNibName:#"Subview"
bundle:nil
subviewPosition:d.Position ];
//Add the SubViewControllers view to this view.
[subsvc.view setFrame:CGRectMake((d.Position-1)*315, 0, 315, 400)];
[self.view addSubview:subsvc.view];
}
[self setDefaultFrame: CGRectMake(0, 0, 640, 400)];
}
return self;
}
This works perfectly and, previously, hadn't even caused any trouble with the buttons that were on the associated view, however, now all UIButtons crash the app when tapped.
The initialization function for SubViewController, as well as the viewDidLoad method contain nothing other than the standard, auto-generated code that is added when you create a new ViewController.
What can I do to either fix or diagnose this problem?
See my comments in your code:
{
SubViewController* subsvc = [[SubViewController alloc] initWithNibName:#"Subview" bundle:nil subviewPosition:d.Position ];
//!i: By default, subsvc is a __strong pointer, so your subview has a +1 retain count
// subsvc owns subsvc.view, so subsvc.view has a +1 retain count as well
//Add the SubViewControllers view to this view.
[subsvc.view setFrame:CGRectMake((d.Position-1)*315, 0, 315, 400)];
[self.view addSubview:subsvc.view];
//!i: This bumps subsvc.view to +2, as self.view strong-references it
//!i: subsvc is going out of scope, so the reference count on subsvc will drop
// to 0 and it is dealloc'd. subsvc.view's retain count drops to +1, as it
// is still referenced by self.view
//
// Most likely, in -[SubViewController dealloc], you were not doing a
// setTarget:nil, setAction:nil on the button. Thus, the button now
// has a dangling pointer and will crash when hit
}
To fix this, add each SubViewController instance to an array owned by the master view controler. That will keep the SubViewController instances around to receive the button taps.
Make sure in your dealloc you call:
[button removeTarget:nil
action:NULL
forControlEvents:UIControlEventAllEvents];
Even though you though you didn't need "dealloc's" in ARC, you do because of what iccir explained.

Problem with NSCollectionView

I'm quite a rookie when it comes to Cocoa programming, so I hope some experts can hint me on the right direction with a problem I'm facing.
I have a NSCollectionView that is bound to a NSArrayController (A) of elements having two properties: an URL to a picture and a NSArray (B) of elements of another class.
For every element inside the NSArrayController (A) , I load a subview with a NSImageView that should display the image in the URL and a NSTableView that is bound to the elements of the NSArray (B).
Everything works fine, except for one very important thing: the URL to the picture is not immediately available when I create the subview, it becomes available later, but when it does, I don't see the picture loading in the NSImageView.
I would think of a problem in the KVC/KVO implementation, but the strange thing is that the image loads correctly if, when the URL becomes available, the subview is not visible (e.g in a portion of the scrollview that is not displayed).
Example: The NSScrollview size is such that it can display only two subviews at a time. I add three elements to the NSArrayController (A): the first two images don't load, if I scroll down the scrollview to see the third element, I find the image loaded correctly.
Any ideas about what could cause such a strange behaviour?
Thank you in advance
Luca
series.h
#interface Series : NSObject {
#private
NSMutableString * banner;
}
-(Series*)initWithEpisode:(Episode*)ep;
-(void)setBanner:(NSString*)_banner;
#property (retain, readwrite) NSMutableString *banner;
#end
series.m
#implementation Series
#synthesize banner;
-(Series*)initWithEpisode:(Episode*)ep
{
self = [super init];
if(self){
banner = [[NSMutableString alloc]initWithString:#"some invalid URL"];
}
-(void) setBanner:(NSString*)_banner
{
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
}
-(void)dealloc
{
[super dealloc];
[banner release];
}
SeriesListViewController.m
-(void)VideoChangedStatus:(Episode*)ep{
//This is the delegate called by the object Episode when it retrieves the URL via asynchronous call to a webservice
Series *tmp = [[Series alloc]initWithEpisode:ep];
[[seriesList objectAtIndex:[seriesList indexOfObject:tmp]]setBanner:[ep banner]];
}
The binding is done in the subview nib file, to the NSImageView: I set File's Owner of type NSCollectionViewItem and then bind Valueurl to representedObject.banner
I didn't subclass NSCollectionView nor NSCollectionViewItem
After days of trying I found a solution that works: apparently it's not enough to use the setString method, I need to re-intialize the property inside the setBanner method
-(void) setBanner:(NSString*)_banner
{
banner = [NSMutableString[NSString stringWithFormat:#"some root path/%#", _banner]];
}
Still, I'd be very glad to know if someone has an explanation of why setString was causing that strange (to me) problem and why this solution works.
Thank you
Luca
I’m not sure why you’ve declared banner to be a mutable string — it looks like an immutable string would suffice.
At any rate, when you write a custom setter method you need to send -willChangeValueForKey: and -didChangeValueForKey: to ensure KVO (and hence bindings) compliance:
-(void) setBanner:(NSString*)_banner
{
[self willChangeValueForKey:#"banner"];
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
[self didChangeValueForKey:#"banner"];
}