cocoa: which method I should use to initialize properties instead of awakeFromNib - macos

TableData is a subclass of NSObject as datasource and table delegate for a view-based table. The awakeFromNib method of TableData will run many times because I'm using view-based table. If TableData is a subclass of NSViewController, I can use loadView: to finish my task, but TableData is a subclass of NSObject, my question is:
which method I should use instead of awakeFromNib to initialize TableData properties?

I don't know how you create your window, but you can do in this way:
AppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
fMainWinDelegate = nil;
fMainWinDelegate = [[MainWinDelegate alloc] init];
[fMainWinDelegate showWindow];
}
MainWindowDelegate.m
- (id)initWithWindow:(NSWindow *)AWindow
{
NSLog(#"MainWinDelegate::initWithWindow");
self = [super initWithWindow:AWindow];
if (self) {
// Initialization code here.
NSLog(#"MainWinDelegate::initWithWindow, we have self!");
}
return self;
}
- (void)awakeFromNib
{
NSLog(#"MainWinDelegate::awakeFromNib");
// only for debug and to be sure that is called many times
}
- (void)showWindow {
NSLog(#"MainWinDelegate::showWindow");
if (!self.window) {
[NSBundle loadNibNamed:#"MainWin" owner:self];
NSLog(#"MainWinDelegate::showWindow init part");
// do your init here
}
[self.window makeKeyAndOrderFront:self];
NSLog(#"MainWinDelegate::showWindow end");
}
This is the log:
MainWinDelegate::initWithWindow
MainWinDelegate::initWithWindow, we have self!
MainWinDelegate::showWindow
MainWinDelegate::awakeFromNib
MainWinDelegate::showWindow init part
MainWinDelegate::showWindow end

You might choose either:
#interface MONTableData : NSObject
// a designated initializer:
- (id)init;
- (id)initWithCoder:(NSCoder *)pCoder;
// or when the `TableData`'s input data source is set:
- (void)setPhotoAlbum:(MONPhotoAlbum *)pPhotoAlbum;
#end

Related

Bindings working from IB, not from my addObserver...

My Document-based app was created without storyboards in Xcode 6.3 so it began life without a window controller (I still don't have a window controller -- just trying to give some background and context).
I have a class structure implemented for working with a gradient and storing it's formative values in my document.
My Document class holds a reference to a Theme object.
My Theme class holds a reference to a Gradient object.
My Gradient class holds a reference to an NSNumber for the start point of the gradient.
In IB an NSSlider is bound to File's Owner, with Model Key Path of "self.theme.gradient.startPointX"
This works fine, as evidenced by Gradient.m -didChangeValueForKey logging out the specific key whose value is being changed.
So why doesn't a similar notification occur in my Document class when the slider for the gradient start point is changed after I have asked to observe it?
Document.m
- (instanceType)init {
self = [super init];
if (self) {
self.theme = [[MyTheme alloc] init];
// first attempt -- not live when second attempt is compiling
[self addObserver:self
forKeyPath:#"theme.gradient.startPointX"
options:NSKeyValueObservingOptionNew
context:#"myDocument"];
// second attempt -- not live when the first attempt is compiling
[self.theme.gradient addObserver:self
forKeyPath:#"startPointX"
options:NSKeyValueObservingOptionNew
context:#"myDocument"];
}
return self;
}
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(#"Document notified that \"%#\" has changed.", key);
}
-
Theme.m
- (instancetype)init
{
if (self = [super init]) {
self.gradient = [[Gradient alloc] init];
}
return self;
}
-
Gradient.h
#interface Gradient : NSObject
#property (strong, nonatomic) NSNumber *startPointX;
#end
-
Gradient.m
- (instancetype)init
{
if (self = [super init]) {
self.startPointX = #(0.47f);
}
return self;
}
- (void)didChangeValueForKey:(NSString *)key
{
if ([key isEqualToString:#"startPointX"]) {
NSLog(#"Gradient notified a change to %# has occurred.", key);
}
It turns out that if you implement -didChangeValueForKey: it blocks/suspends normal notification of those properties you might be observing.
Commenting out my Gradient.m implementation of
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(#"'%#' has been changed.", key);
}
caused my observations from Document to begin working fine.

Can an array controller take inputs before "add" is pressed?

In Cococa,
Can I link input fields (say a textField) to my array controller so that a new object is added with attributes already populated?
Thanks
Try the below code as it is done for one textfield:-
Header File
#import <Cocoa/Cocoa.h>
#interface testWindowController : NSWindowController
{
IBOutlet NSArrayController *arrayController;
NSMutableArray *array;
}
#property(readwrite,retain)NSMutableArray *array;
#end
Implememntation file:-
#import "testWindowController.h"
#interface testWindowController ()
#end
#implementation testWindowController
#synthesize array;
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
NSString *str=#"testValue";
self.array=[[NSMutableArray alloc]init];
NSMutableDictionary *dict=[NSMutableDictionary dictionary];
[dict setObject:str forKey:#"valueText"];
[self.array addObject:dict];
[self setArray:self.array];
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
-(NSString *)windowNibName
{
return #"testWindowController";
}
#end
XIB file:-
Follow below steps:-
1) In this file take one arraycontroller and textfield and then connect your arraycontroller to FileOwner.
2) Then in Binding Inspector bind arraycontroller to FileOwners and mention model key path as array.
3) Now bind your textfield to arraycontroller and model key path as "key:" whose name is valueText.

Zooming MkMapView from another class

I have a ViewController with UITableView *myTable and MKMapView *myMap designed in xib, but the table delegate/datasource and the map delegate are in another class, named SubClass. When I press a button in ViewController the SubClass parse in the tablecells latitude and longitude from a xml remote file, and now I want to zoom myMap into this coordinates every time I select the rows of myTable: Well, I can't find a way to call this zoom FROM SubClass. This is, simplified, my code:
ViewController.h
// ViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import "SubClass.h"
#interface ViewController : UIViewController {
IBOutlet UITableView *myTable;
IBOutlet MKMapView *myMap;
SubClass *subClassIstance;
}
- (void)buttonPressed:(id)sender
#property (nonatomic, retain) IBOutlet MKMapView *myMap;
ViewController.m
// in ViewController.m
- (void)buttonPressed:(id)sender {
subClassIstance = [[SubClass alloc] init];
myTable.delegate = SubClass;
myTable.dataSource = SubClass;
[myTable reloadData];
subClassIstance = [[SubClass alloc] loadMap:myMap];
}
SubClass.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface SubClass : NSObject <UITableViewDataSource, UITableViewDelegate, MKMapViewDelegate> {
}
- (void)loadValues;
- (id)loadMap:(MKMapView *)mapView;
- (id)zoomTheMap:(NSMutableString *)string1 :(NSMutableString *)string2 :(MKMapView *)mapView; // IS IT RIGHT???
SubClass.m
- (id)init{
self = [super init];
if ( self != nil ) {
[self loadValues];
}
return self;
}
- (void)loadValues {
// CODE TO PARSE VALUES OF LONGITUDE AND LATITUDE TO PASS IN THE TABLE CELLS
latitudeFromLoadValues = // NSMutableString parsed value from a xml remote file
longitudeFromLoadValues = // NSMutableStringparsed value from a xml remote file
}
- (id)loadMap:(MKMapView *)mapView
{
if (self) {
mapView.delegate = self; // CODE TO LOAD ANNOTATIONS AND OTHER STUFF. IT WORKS!
}
return self;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
latitudeFromLoadValues = [dataParsed objectAtIndex:indexPath.row];
longitudeFromLoadValues = [data2Parsed objectAtIndex:indexPath.row];
[self zoomTheMap:latitudeFromLoadValues :longitudefromLoadValues :???]; // IS IT CORRECT? WHAT IS THE RIGHT *MKMAPVIEW?
}
- (id)zoomTheMap:(NSMutableString *)string1 :(NSMutableString *)string2 :(MKMapView *)mapView {
NSLog(#"%#",string1);
NSLog(#"%#",string2);
MKCoordinateRegion region;
region.center.latitude = [string1 floatValue];
region.center.longitude = [string2 floatValue];
region.span.latitudeDelta = 2.0;
region.span.longitudeDelta = 2.0;
// I KNOW, I HAVE TO CALL myMap from ViewController! But with an istance?
mapView.delegate = self;
mapView.region = region;
return self;
}
Well, the rest of code works! I can see *myMap in ViewController loaded with some annotations declared in SubClass and *myTable loaded with cells populated with latitude and longitude parsed in SubClass; I can also see correct longitude and latitude passed in string1 and string2 but when I select the single table cell I don't see myMap zooming, I think I am using the wrong method. Can U help me, please?
loadMap shouldn't return self, only init methods should do that.
In buttonPressed you allocate a new SubClass, do some stuff to it, then allocate another SubClass and call its loadMap function. The last line should be [subClassIstance loadMap:myMap], but you'll also want to reconsider allocating a new SubClass every time that button is pressed.
I think you're really going about this the wrong way. Why do you need a SubClass (terrible name BTW, it says nothing about what it is for)? What class does it extend? If the ViewController has the MKMapView, it is usually the one to issue commands to the map. I can understand you having a separate datasoucre for the tableview, but not the rest. If you make the VC its own table and map delegate you'll simplify things a lot.
If you really want to have a subclass in your code then you should be calling loadMap on the instance you created on the first line of buttonPressed
- (void)buttonPressed:(id)sender {
subClassIstance = [[SubClass alloc] init];
myTable.delegate = SubClass;
myTable.dataSource = SubClass;
[myTable reloadData];
[subClassIstance loadMap:myMap];
}
and your loadMap would look like
- (void)loadMap:(MKMapView *)mapView
{
mapView.delegate = self;
}
However if that's all loadMap does you don't need a function for that, you could just make buttonPressed do it all.
- (void)buttonPressed:(id)sender {
subClassIstance = [[SubClass alloc] init];
myTable.delegate = SubClass;
myTable.dataSource = SubClass;
[myTable reloadData];
myMap.delegate = subClassIstance;
}
Example init function:
- (id)initiWithMapView: (MKMapView)* mapView
{
self = [super init];
if(self)
{
theMap = mapView;
theMap.delegate = self;
[self loadValues];
....
}
return self;
}
If you use this you won't have to set the map delegate or return self all the time and you can use theMap (as declared in your answer) in every function.
Well, I have found a simply solution, for those interested: first, I have defined a generic MKMapView *theMap in my SubClass.h, that now looks like:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface SubClass : NSObject <UITableViewDataSource, UITableViewDelegate,
MKMapViewDelegate> {
MKMapView *theMap // NEW CODE!!!
}
- (void)loadValues;
- (id)loadMap:(MKMapView *)mapView;
- (id)zoomTheMap:(NSMutableString *)string1 :(NSMutableString *)string2 :(MKMapView *)mapView;
In loadMap method I have compared *theMap to mapView called by the SubClassIstance in VC (my *myMap that I want to zoom), so now we have:
- (id)loadMap:(MKMapView *)mapView
{
if (self) {
mapView.delegate = self;
theMap = mapView; // NEW CODE !!!
// CODE TO LOAD ANNOTATIONS AND OTHER STUFF. IT WORKS!
}
return self;
}
In didSelectRowAtIndexPath I have passed *theMap as mapView argument of zoomTheMap method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
latitudeFromLoadValues = [dataParsed objectAtIndex:indexPath.row];
longitudeFromLoadValues = [data2Parsed objectAtIndex:indexPath.row];
[self zoomTheMap:latitudeFromLoadValues :longitudefromLoadValues :theMap]; // NEW CODE !!!
}
The zoomTheMap method doesn't change, and now, "magically", every time I press a row of my table, the *myMap designed in the VC xib (but with delegate in SubClass) zooms into the coordinates stored in the cells:
- (id)zoomTheMap:(NSMutableString *)string1 :(NSMutableString *)string2 :(MKMapView *)mapView {
MKCoordinateRegion region;
region.center.latitude = [string1 floatValue];
region.center.longitude = [string2 floatValue];
region.span.latitudeDelta = 2.0;
region.span.longitudeDelta = 2.0;
mapView.delegate = self; // here mapView is *theMap passed in didSelectRowAtIndexPath, AKA original mapView istance used to delegate *myMap in VC
[mapView setRegion:region animated:YES];
return self;
}
Maybe its not an "elegant" way, but it now works! ;=)

IBOutlet for NSOutlineView doesn't point a valid instance

I created a new Cocoa application project in Xcode then add a NSOutlineView and a NSTextView objects onto window. Those two objects were subclassed as MyOutlineView and MyTextView. After that I made two outlets for them and wrote code like below.
The problem, I found, is application has two different MyOutlineView instances in runtime. Working(valid) outline view instance is not equal to the myOutlineView outlet instance. What am I missing?
//
// AppDelegate.h
#import <Cocoa/Cocoa.h>
#import "MyOutlineView.h"
#import "MyTextView.h"
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (weak) IBOutlet MyOutlineView *myOutlineView;
#property (unsafe_unretained) IBOutlet MyTextView *myTextView;
#end
//
// AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)n
{
NSLog(#"AppDelegate.myOutlineView(INVALID)::%#", _myOutlineView);
NSLog(#"AppDelegate.myTextView::%#", _myTextView);
}
#end
//
// MyOutlineView.h
#import <Cocoa/Cocoa.h>
#interface MyOutlineView : NSOutlineView <NSOutlineViewDataSource>;
#end
//
// MyOutlineView.m
#import "MyOutlineView.h"
#implementation MyOutlineView
- (id)initWithCoder:(NSCoder *)aDecoder
{
// This method is called first.
self = [super initWithCoder:aDecoder];
NSLog(#"MyOutlineView initWithCoder(INVALID)::%#", self);
return self;
}
- (id)initWithFrame:(NSRect)frame
{
// This method is also called but through a different instance with first one.
self = [super initWithFrame:frame];
NSLog(#"MyOutlineView initWithFrame(valid)::%#", self);
return self;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
NSLog(#"MyOutlineView data source delegate(valid)::%#", self);
return 0;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
return nil;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
return NO;
}
#end
//
// MyTextView.h
#import <Cocoa/Cocoa.h>
#interface MyTextView : NSTextView
#end
//
// MyTextView.m
#import "MyTextView.h"
#implementation MyTextView
- (id)initWithCoder:(NSCoder *)aDecoder
{
// This method is called.
self = [super initWithCoder:aDecoder];
NSLog(#"MyTextView initWithCoder::%#", self);
return self;
}
- (id)initWithFrame:(NSRect)frame
{
// But this method is NOT called at all.
self = [super initWithFrame:frame];
NSLog(#"MyTextView initWithFrame::%#", self);
return self;
}
#end
Output:
MyTextView initWithCoder:: [MyTextView: 0x10013be80]
MyOutlineView initWithCoder(INVALID):: [MyOutlineView: 0x10014bc90]
MyOutlineView initWithFrame(valid):: [MyOutlineView: 0x1001604a0]
MyOutlineView data source delegate(valid)::[MyOutlineView: 0x1001604a0]
AppDelegate.myOutlineView(INVALID):: [MyOutlineView: 0x10014bc90]
AppDelegate.myTextView:: [MyTextView: 0x10013be80]
Because of this, I have to put "AppDelegate.myOutlineView = self;" into MyOutletView's implementation wherever it calls related methods of AppDelegate. It does not seem natural.
Xcode doesn't seem to let you set an outline view's delegate or data source to itself.
So I'm guessing you're doing something like this:
Which is to say: instantiating a second copy of your custom outline view class.
Here's the output from this setup:
2012-09-26 14:11:34.511 testproj[30255:403] -[MyOutlineView initWithCoder:]
2012-09-26 14:11:34.531 testproj[30255:403] -[MyOutlineView initWithFrame:]
By removing the extra (highlighted) instance of My Outline View, the initWithFrame: line goes away.
To make the outline view its own delegate, do this instead:
- (void) awakeFromNib {
self.delegate = self;
}
That said, the point of the Delegation pattern is avoiding the need to subclass. If you do need an outline view subclass, try overriding the NSOutlineView / NSTableView methods directly, instead of using the delegate protocol.
I can't reproduce your problem. I dropped all your code that you posted into a test app, and I only get one instantiation of each object. Neither of the initWithFrame methods are getting called when I try it. My output is:
2012-09-26 09:00:38.945 TextViewDoubleInstantiationProblem[451:303] MyTextView initWithCoder::<MyTextView: 0x100123990>
Frame = {{0.00, 0.00}, {381.00, 182.00}}, Bounds = {{0.00, 0.00}, {381.00, 182.00}}
Horizontally resizable: NO, Vertically resizable: YES
MinSize = {381.00, 182.00}, MaxSize = {463.00, 10000000.00}
2012-09-26 09:00:38.953 TextViewDoubleInstantiationProblem[451:303] MyOutlineView initWithCoder(INVALID)::<MyOutlineView: 0x101a1cb90>
2012-09-26 09:00:39.005 TextViewDoubleInstantiationProblem[451:303] AppDelegate.myOutlineView(INVALID)::<MyOutlineView: 0x101a1cb90>
2012-09-26 09:00:39.005 TextViewDoubleInstantiationProblem[451:303] AppDelegate.myTextView::<MyTextView: 0x100123990>
Frame = {{0.00, 0.00}, {381.00, 182.00}}, Bounds = {{0.00, 0.00}, {381.00, 182.00}}
Horizontally resizable: NO, Vertically resizable: YES
MinSize = {381.00, 182.00}, MaxSize = {463.00, 10000000.00}
Do you have any other code in your app that you're not showing?
The calls to initWithCoder: come from loading objects that are defined in a nib file. I assume that's what you want to have happen since you mention creating outlets. In that case, they call to initWithFrame: strikes me as more likely to be "invalid" than the coder one.
I'd set a breakpoint in initWithFrame: and trace where that call is coming from in order to identify the extra allocation.

NSMutableDictionary setObject:forKey - custom class

I am using this in a UINavigation environment.
I have customClassA. It inherits customClassB and one of its object is a NSMutableDictionary.
I alloc and init customClassA in a viewController, then for adding data, I am pushing a new viewController into the stack. The addNewDataViewController sends the newly added data, a customClassB object back by its delegate. Everything works fine so far.
customClassA has to store the returned object (customClassB) into its NSMutableDictionary object with a key (an NSString created from NSDate).
I get "mutating method sent to immutable object" error and can't think of any solution.
Any help is appreciated.
Thanks.
===========================
interface customClassA : NSObject
{
NSDate date;
NSArray *array; // will contain only NSString objects
}
// and the rest as customary
...
#import "customClassA.h"
interface customClassB : NSObject
{
NSString *title;
NSMutableDictionary *data; // will contain values of customClassA with keys of NSString
}
// and the rest as customary
...
#import "customClassB"
#interface firstViewController : UITableViewController <SecondViewControllerDelegate>
- (void)viewDidLoad
{
self.customClassB_Object = [customClassB alloc] init];
// and the rest...
}
- (void)secondViewControllerDidSaveData:(customClassA *)aData
{
[self.customClassB_Object.data setObject:aData forKey:[NSString stringWithFormat:#"%#", aData.date]];
// update tableView
}
Make sure you are initializing the NSMutableDictionary with something like
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] init];
It would appear that your NSMutableDictionary is getting created with an NSDictionary instance instead of a NSMutableDictionary
Althoguh I added the following code to customClassB implementation, it still didn't work.
#implementation customClassB
- (id)init
{
self = [super init];
if (self)
self.data = [NSMutableDictionary alloc] init];
return self;
}
so I added two custom methods to my customClassB implementation, as well as in the header file:
- (void)appendData:(customClassA *)aData;
- (void)removeDataWithKey:(NSString *)aKey;
and instead of manipulating the data dicionary of customClassB in my viewController, I simply call that method and pass the data object to the class and it did the trick.

Resources