Losing models between controllers while using ARC - cocoa

I have two controllers that are delegates of each other, and I am using ARC. The first controller retrieves an NSMutableArray of model objects from a database and then holds them for use by other objects. I have tested to make sure it is retrieving the objects correctly.
The second controller is supposed to obtain the model array from the first controller for further processing, but the models often disappear in the meantime. I set this up in the first controller:
#interface FirstController : NSObject {
NSMutableArray *modelArray;
#end
#property (nonatomic, copy) NSMutableArray *modelArray; //I also tried (strong).
#implementation
- (void)awakeFromNib {
modelArray = [[NSMutableArray alloc]initWithCapacity:1];
[modelArray addObjectsFromArray:[delegate MySQLQuery:#"SELECT * FROM reports" forModelObjects:#"Report"]]; //Delegate here refers to a third controller
NSLog(#"FirstController: %ld", [modelArray count]); //Everything OK here
}
#synthesize modelArray;
But when I call this in the second controller:
NSMutableArray *newArray = [[NSMutableArray alloc]initWithCapacity:1];
newArray = [delegate modelArray]; // Delegate here refers to FirstController
NSLog(#"SecondController: %#", newArray);
output is as follows most of the time (though occasionally it works):
FirstController: 28
SecondController: (null)
It looks to me like ARC is deallocing modelArray in FirstController, but I don't how to stop it. What am I doing wrong?

Related

Page number in Page-Based Interface with WatchKit (Watch)?

I created a WatchKit app with a Page-Based Interface.
There are 3 pages and each one is connected to my InterfaceController.swift class (which extends WKInterfaceController).
My question: inside InterfaceController.swift how can I detect the page number of the current view?
If you use
func presentControllerWithNames(_ names: [AnyObject],
contexts contexts: [AnyObject]?)
You just have to pass the number of the page in the context, so you can store it, and retrieve it later on. This is the non storyboard way.
If you use storyboard, it is the same, you just have to use the other method
func contextsForSegueWithIdentifier(_:inTable:rowIndex:)
And pass your page index in the context of each controller
It can be achieved this way,
Present InterfaceController in pageNavigation with Appropriate Context Values,
[self presentControllerWithNames:#[#"TestInterfaceController",#"TestInterfaceController",#"TestInterfaceController"] contexts:#[#"Page 1",#"Page 2",#"Page 3"]];
Then create a NSString property to track page and label to display it,
#property (nonatomic, strong) NSString *currentContext;
#property (weak, nonatomic) IBOutlet WKInterfaceLabel *pageLabel;
In awakeWithContext assign it to NSString property,
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
NSLog(#"%#",context);
self.currentContext = context;
}
Display it on willActive,
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
[super willActivate];
NSLog(#"%# willActivate",self.currentContext);
[self.pageLabel setText:self.currentContext];
}
You can also detect when page didDeactivate,
- (void)didDeactivate {
// This method is called when watch view controller is no longer visible
[super didDeactivate];
NSLog(#"%# didDeactivate",self.currentContext);
}
Edit :
If page navigations are configured using Storyboard Segue then override this method in Source IntefaceController from where you created model segue to destination controller to provide contexts,
- (NSArray *)contextsForSegueWithIdentifier:(NSString *)segueIdentifier {
NSArray *contexts = nil;
if ([segueIdentifier isEqualToString:#"MyPageNavigation"]) {
contexts = #[#"Page 1",#"Page 2",#"Page 3"];
}
return contexts;
}

NSArrayController, NSPopupButton proper bindings

I just started Mac programming coming over from iOS and was playing around with bindings.
I'm trying to make a simple directory popup that shows a history of recently selected directories and last element would read other... which will open the opendialog box.
I can't seem to figure out how to Bind an NSPopupButton to my Model though.
Its setup like this:
MainUIViewController, NSController, NSObject Controller all wired up in the nib
I do connect an outlet in MainUIViewController to the Directory Array Controller in the NIB
I have a class for eachDirectory, and a class for DirectoryArrayController(NSObject) I bind the NSPopupButton on view this way:
and I have the Directory Array Controller bound to the Directory Popup Array Controller thus
Here is the .h file that is connected to the Directory Popup Array Controller
#interface DirectoryPopupArrayController : NSObject
#property (weak) IBOutlet NSPopUpButton *directoryPopupButton;
#property (nonatomic) IBOutlet NSMutableArray *allDirectoryHistory;
#property (nonatomic) eachDirectory *currentlySelectedDirectory;
#end
I fill some sample directory info with the following code in the corresponding .m file
- (void)awakeFromNib {
[super awakeFromNib];
//testing sample directories
self.allDirectoryHistory = [[NSMutableArray alloc] initWithCapacity:10];
NSString *name;
eachDirectory *newDirectoryName;
for (int i = 0; i < 5; i++) {
name = [NSString stringWithFormat:#"directory %d", i];
newDirectoryName = [[eachDirectory alloc] initWithDirectoryName:name];
[self.allDirectoryHistory addObject:newDirectoryName];
}
}
and here is the code for the eachDirectory.h
#interface eachDirectory : NSObject
#property (nonatomic) NSString *directoryPath;
#property (nonatomic) NSString *directoryVisibleName;
-(id) initWithDirectoryName:(NSString *)newName;
#end
Now When I go to my code if I place the code for creation of the Array and bind the Array controller directly to the UIViewController.m file things seem to work fine.
What I want to do is handle all the array stuff in a separate class file and only get back the final directory choice to the main controller. When I bind the NSArrayController to the Object controller in the NIB as described above I get nothing showing in the popup and I don't understand why!
Any Help is greatly appreciated, Sorry for the long-winded post - just wanted to make myself clear.

Can't load an NSMutableArray with UIView objects

On my game board (which is called GameViewController), I have six Seats (in the xib) which are nothing more than subclassed UIViews (so I also have Seat.h and .m files in my project). When the game board gets initialized these seats also get created (thanks to the xib). I want to have the seats loaded into an NSMutableArray so that I can use them later. For some reason I can't get it to work.
In my GameViewController header file I've added NSMutableArray *seats; as an instance variable and included #class Seat; above the interface declaration.
In my awakeFromNib method of the GameViewController I have seats = [[NSMutableArray arrayWithCapacity:6] retain]; So the array should be initialized when the game board appears.
Then in my Seat header file I've included GameViewController *controller; as an instance variable and included #class GameViewController above the interface declaration. I've also added and synthesized a property for the GameViewController.
In the Seat's awakeFromNib method I have [controller registerSeat:self];
This calls a method in my GameViewController that has only one line: [seats addObject:seat]; This should add the seat to the array. But for some reason this method never seems to get called. Or if it does, I can never tell. When I debug, focus never goes to the registerSeat method even though the seats do get added to the board. I hope this all makes sense. If the code is needed, I can provide it. It might be easier to do that anyway. What do you guys think? I'm stumped at the moment.
The method declaration is as follows:
- (void) registerSeat:(Seat *)seat;
GameViewController.h:
#import <UIKit/UIKit.h>
#class Seat;
#interface GameViewController : UIViewController {
NSMutableArray *seats;
}
- (void) registerSeat:(Seat *)seat;
#end
GameViewController.m
#import "GameViewController.h"
#import "Seat.h"
#implementation GameViewController
- (void)awakeFromNib {
seats = [[NSMutableArray arrayWithCapacity:6] retain];
}
- (void) registerSeat:(Seat *)seat {
[seats addObject:seat];
NSLog(#"seat has been registered");
}
#end
Seat.h:
#import <UIKit/UIKit.h>
#class GameViewController;
#interface Seat : UIView {
GameViewController *controller;
}
#property (nonatomic, retain) IBOutlet GameViewController *controller;
#end
Seat.m:
#import "Seat.h"
#import "GameViewController.h"
#implementation Seat
#synthesize controller;
- (void) awakeFromNib {
[controller registerSeat:self];
}
#end
What you're doing here is what Xcode 4's IBOutletCollection is designed to solve. In your NIB, select all of your Seat views (these should be called SeatView, BTW. Seat should be a model class, not a view class). Drag the selected group to your GameViewController header and request an IBOutletCollection. This will wire them all as a random-ordered array. (Why they chose to make it a random-ordered array is beyond me; it's a somewhat insane construct, but it exactly matches what you're trying to do above.)
As #highlycaffeinated notes, the most likely reason for your current code failing is that you've failed to wire your controller and it's nil. When "nothing happens" in Objective-C, it's almost always because you're talking to nil.

Custom datasource with a NSComboBox not displaying anything

Greetings I have the following problem trying to set a datasource in an NSComboBox.
This is my custom datasource class:
#interface CComboDatasource : NSObject <NSComboBoxDataSource> {
#private
NSMutableArray* values;
}
#property (nonatomic,retain) NSMutableArray* values;
-(int)itemCount;
#end
#implementation CComboDatasource
#synthesize values;
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
values=[[NSMutableArray alloc] init];
[values addObject:#"A"];
[values addObject:#"B"];
[values addObject:#"C"];
}
return self;
}
- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox
{
return [values count];
}
- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index
{
return [values objectAtIndex:index];
}
- (void)dealloc
{
[values release];
[super dealloc];
}
#end
Later in another file I connect my IBOutlet with my NSComboBox object (c_box) and I set the datasource (CComboDatasource* data_source).
[c_box setUsesDataSource:TRUE];
[c_box setDataSource:data_source];
[c_box setEditable:NO];
After the previous actions nothing is displayed in the combo box, what am I doing wrong?
What you have looks basically right to me. I can think of a few things you could try:
1) Try temporarily replacing "return [values count]" with "return 5" and replacing "return [values objectAtIndex:index]" with "return #"arbitraryString"". If "arbitraryString" then shows up in the combobox, you'll know the problem is with the "values" array.
2) Try initializing the "values" array like this:
values = [NSMutableArray array];
(It's a convenience method offered in NSArray.)
If you stick with an alloc-init method, you should make a separate temporary array that way, assign it to "values," then release it. Otherwise, since you've propertized "values" with "retain," you're retaining it twice.
3) Try adding this line at the end of your c_box calls:
[c_box reloadData];
And any time you change the data source array, call this again.
4) I don't see why separating the data source class from the class controlling the combobox should be a problem, but if it's still not working, try making the window/view controller that owns the combobox outlet the class that implements the NSComboBoxDataSource protocol (the numberOfItemsIn and objectValueFor methods), and either put "values" in this controller class or give this class access to "values."
Hope that helps.
Ok I found the problem ,in order by the custom datasource class to work u need
Create an NSObject and drag it to your editor
Change the type to your custom datasource class
Declare your Datasource as IBOutlet CustomDatasourceClass* myclass
Connect the Object with the previous outlet
Link your NScomboBox datasource (in IB designer) to the CustomDatasourceClass object
I have problem with comboBox:objectValueForItemAtIndex: because I have 10 combo box, every combo box I checking by:
if (aComboBox == _myCombo)
8 combo box works fine, but 2 not. I don't know what I'm doing wrong and why others work. I thinking about this problem about 2 weeks. I'm trying to delete and create new with different steps, but nothing help.
The solution is to reloadData before select option in awake from nib.
[_myCombo reloadData];

Unable to write to NSTextField from Model Controller object

After always running monolithic blocks of code from within my AppController object, I've just learned how to modularize my code using separate model controller objects (so much neater and less confusing to work with :-))
My simple test app has a main AppController and two model objects (Model1 and Model2). I can successfully pass data from the AppController to the models, and the models themselves can run methods and process the passed data 'internally' as they were intended to do -- but I can't get them to communicate with a darned NSTextField in the UI. Here's the relevant parts of my code:
In AppController.m #import "AppController.h"
#implementation AppController
- (IBAction)passObjectsToModelController:(id)sender
{
NSString *stringToPass = #"Hello from Model2 :-)";
int numToPass=12345;
Model2 *ObjController2 = [[Model2 alloc]initWithStuff:stringToPass:numToPass];
[ObjController2 release];
}
#end
...in Model2.h
#import
#interface Model2 : NSObject
{
IBOutlet NSTextField *passedStringField;
}
- (id)initWithStuff:(NSString*)passedString :(int)passedNum;
#end
...and finally in Model2.m
#import "Model2.h"
#implementation Model2
- (id)initWithStuff:(NSString*)passedString :(int)passedNum
{
if(self = [super init])
{
NSLog(#"now inside 'Model2' controller...");
NSLog(#"the passed string reads: %#",passedString); //••• this works •••
NSLog(#"the passed number is:%d",passedNum); //••• this works •••
[passedStringField setStringValue:passedString]; //••• WTF!!... this DOESN'T work! •••
// do something internally with passedNum here...
}
return self;
}
#end
Both model objects have outlets to the common NSTextField and I've control-dragged from both objects to the field and connected them. My AppController doesn't know about the NSTextField (and I assume, doesn't even want to know). No IB connections have been made between the controller object and model objects.
NSLog tells me that the model objects are being created, and that the passed values are making it that far... but not from there into the text field in the GUI window. I'm not getting any compiler errors or warnings. Am I missing some kind of 'setTarget:' call perhaps?
Any help/ideas would be much appreciated. Thanks :-)
Aside from the lack of MVC that mihirsm mentions, the actual problem is that you're trying to access an outlet in an -init method.
When a object is initialized, outlets are not guaranteed to be connected.
If you want to set the value of an NSTextField declared as an outlet, you should implement -awakeFromNib, which is called when the nib has been loaded and all outlets are guaranteed to be live.
in Model1.h:
#interface Model1 : NSObject
{
IBOutlet NSTextField* passedStringField;
NSString* modelString;
}
- (id)initWithString:(NSString*)passedString number:(int)passedNum;
#end
in Model1.m:
#implementation Model1
- (id)initWithString:(NSString*)passedString number:(int)passedNum
{
if(self = [super init])
{
//copy the string to our ivar
modelString = [passedString copy];
}
return self;
}
//awakeFromNib is called when our outlet is live
- (void)awakeFromNib
{
[passedStringField setStringValue:modelString];
}
//don't forget to release the string, because we created it using -copy
- (void)dealloc
{
[modelString release];
}
#end
The Controller sits between the Model and the View. The Model should not communicate with the View.
It should be the job of the Controller to pass any incoming values from the View to the Model. The Model then processes the data and sends back to the Controller which then updates the View with the new data.
So, in your code you should only have one IBOutlet for the TexField declared in the AppController.
Given all this, I am not exactly sure why the TextField is not being updated. From the given code looks like it should. Maybe multople IBOutlets are causing some issue? Can you try with only one Model having the IBOutlet?

Resources