Creating an instance of an instance of another class - objective-c

I'm trying to break some of my "super objects" into more manageable classes that have single (or at least limited) responsibility.
One problem I've just run into is making an object of a specific instance of a UIBarButtonItem. In the class it is in now I first define a UIButton, and then all of the images that act as icons for that button as subviews (for instance the button represents access/control to a device, and I use the button image to show the current signal strength of that device). Also that button is listening for NSNotifications from the device object to represent the signal strength changing, or if the device disconnects. And pressing the button sends a message to the device to disconnect. All of this code works perfectly fine now as a property of the RootViewController. However, I want to pull it out into its own class as the button is shared by several classes, and it just clutters up the controller with unnecessary methods.
I tried making an separate class with an init like below. However, this doesn't work as the self used for the button isn't the same self that is ultimately created by [UIBarButtonItem alloc] and when either the NSNotification or the button press try to send a message to the selector of "self", that object has already been dealloced. The problem is, I'm not sure how to create a object (as defined by the class) that is just an instance of another class, as opposed to a property of an object (as it currently is for the RootViewController).
Edit and additional explanation of my problem
MyClass is currently subclass of UIBarButtonItem. However, I'm not trying to use it like this: [[MyClass alloc] initWithCustomView:]. I want [MyClass alloc] init] by itself to completely create the custom view - in other words the whole point of this class is to completely contain all that is necessary for this button to create itself, manage its subviews, and take the appropriate action when it is pressed. (I could easily make MyClass an NSObject with a public method like [MyClass setupButton] and a public property of type UIBarButtonItem. However, I think that looks wrong because then the class is only there to create the button, but it is not the button itself.)
#interface MyClass : UIBarButtonItem
#end
#implementation MyClass
- (id)init {
if (self = [super init]) {
UIImage *defaultButton = [[UIImage imageNamed:#"...
UIImage *defaultButtonPressed = [[UIImage imageNamed:#"....
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 40, 30)];
[button setBackgroundImage:defaultButton forState:UIControlStateNormal];
[button setBackgroundImage:defaultButtonPressed forState:UIControlStateHighlighted];
[button addTarget:self action:#selector(deviceButtonPressed) forControlEvents:UIControlEventTouchUpInside];
//Then several UIImageViews that are added as subviews of the button, initially hidden
//Then set up the NSNotification listener
//Finally
self = [[UIBarButtonItem alloc] initWithCustomView:button];
}
return self;
}
//Then several functions to handle hiding and unhiding the subviews depending on the received device notifications, and a function to handle the button press and sending the message back to the device.

This is not how initialization works in Cocoa. Please read "Initialization" in the Cocoa Core Competencies guide.
Your object has already been allocated when this init method is run. You should not be reassigning the self pointer to another allocation.
Your class should first call its superclass's designated initializer self = [super initWithWhatever:obj];, then set up its own properties.

It seems to me that you want to extend UIBarButtonItem, not create an instance of it in your init method. Try changing your class declaration (in your class's .h file) from this:
#interface MyClass : NSObject
to this:
#interface MyClass : UIBarButtonItem
Then just return self in your init method. Setting self to a value is usually a bad idea.
If you're unsure about what's going on here, you're creating a subclass of UIBarButtonItem. This lets your subclass extend the superclass's functionality. If you're confused, you should take a look at subclassing/class inheritance in object-oriented languages to understand what's going on. This guide documents how classes work in Objective-C.

Related

The right way of setting up MapKit's delegate in a separate class

What is the proper way of setting up a separate delegate class for MapKit?
I have MapView class subclassing MKMapView and bare MapDelegate class conforming MKMapViewDelegate protocol having only one initializer method.
Here is the extract from MapView initialization method I use:
# MapView.m ...
#implementation MapView
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// [self setShowsUserLocation:YES];
[self setDelegate:[[MapDelegate alloc] initWithMapView:self]];
The only method MapDelegate class has is
# MapDelegate.m ...
- (id)initWithMapView:(MapView *)aMapView {
self = [super init];
self.mapView = aMapView;
return self;
}
Having [self setShowsUserLocation:YES]; commented, all works fine - I see the map. If I uncomment this line, my application begins to crash.
What my MapDelegate class is missing?
UPDATE 1: if I don't use a separate class MapDelegate and set just setDelegate:self - all works.
UPDATE 2: Now I understand, that the problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that I need MapDelegate class to live longer than it does now (delegate property has weak attribute). If I do the following:
#property (strong) id delegateContainer;
....
[self setDelegateContainer:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateContainer];
...it works! Is there a better way of retaining MapDelegate life cycle along with the one of MKMapView?
Thanks!
After waiting enough for any answers that could appear here and ensuring original problematic behavior twice more times, I am posting my own answer based on the second update from the question:
The problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that MapDelegate class should be able to be kept alive outside of the scope of question's initWithFrame method because delegate property has weak attribute. The possible solution is to create an instance variable serving as a container for a delegate class, for example:
#property (strong) id delegateClass;
....
[self setDelegateClass:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateClass];
This solves the original problem.
LATER UPDATE
Though it is possible to set MKMapView's delegate in a separate class, I now realize that such model should not be used:
Currently I always prefer to use my controllers (i.e. controller layer in MVC in general) as delegates for all of my View layer classes (map view, scroll view, text fields): controller level is the place where all the delegates of different views can meet - all situated in controller layer, they can easily interact with each other and share their logic with the general logic of your controller.
On the other hand, if you setup your delegate in a separate class, you will need to take additional steps to connect your separate delegate with some controller, so it could interact with a rest part of your logic - this work have always led me to adding additional and messy pieces of code.
Shortly: do not use separate classes for delegates (at least view classes delegates provided by Apple), use some common places like controllers (fx for views like UIScrollView, MKMapView, UITableView or models like NSURLConnection).
I think viewDidLoad would be a better place to set up the map view. It's just a guess, but perhaps the crash is due to the view not being loaded yet.
Of course subclassing MKMapView isn't recommended at all. You would generally put your map as a subview, and set the main view to be the delegate. From the docs:
Although you should not subclass the MKMapView class itself, you can get information about the map view’s behavior by providing a delegate object.
Finally, if you really want to have a separate delegate class, you don't need to set its mapView, as all delegate methods pass the map as an argument.

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.

When is it safe to manipulate screen controls through code in iOS/Objective-C

I have a question regarding iOS (or perhaps more accurately Objective-C) and properties. I have a UIView with a UISegmentedControl, by default it has 3 segments. I have a message which accepts a parameter and based on this parameter I may want to remove one of the segments. In UIView A I do this:
MyViewController *myview = [[[MyViewController alloc] initWithNibName:#"MyViewController" nib:nil] autorelease];
[[self navigationController] pushViewController:myview animate:YES];
[myview showItem:item];
In UIView B this happens in showItem:
-(void) showItem:(Item*)item{
if (item.removeSegment){
[segmentControl removeSegmentAtIndex:0 animate:NO];
}
}
I have noticed that the segment only gets removed when I call showItem after I have pushed it on the navigation controller. When I swap those two line, so I first call showItem and then push the view, the UISegmentedControl still has three segments instead of two.
This just feels wrong, it seems like bad practice that my code will break if someone doesn't call two messages in the right order. Is there a better way to do this? I've been looking at some sort of a property lifecyle that I can use, I am very familiar with this from ActionScript 3, but I have been unable to find anything on the subject.
(as an aside: in AS3 I would make a property, in the setter I don't manipulate any screen controls but call InvalideProperties. My overriden methode CommitProperties will be called once the entire object and child controls have been created. In CommitProperties I check if my property value has changed and this is where I would remove the segment.)
A common way of doing something like this is to create an Item *item property in MyViewController and set that when myview is created. So, your code becomes:
MyViewController *myview = [[[MyViewController alloc] initWithNibName:#"MyViewController" nib:nil] autorelease];
myview.item = item;
[[self navigationController] pushViewController:myview animate:YES];
MyViewController would then use that property in its viewWillAppear: method to configure its own segment control.
I think what you are falling prey to is myview->segmentControl doesn't exist until myview.view is referenced because of the lazy load of the view.
-(void) showItem:(Item*)item{
[self view]; // NO OP TO FORCE LOAD!!
if (item.removeSegment){
[segmentControl removeSegmentAtIndex:0 animate:NO];
}
}
Should work for you. Hope this helps!

Calling a method in a UIViewController from a UIButton in a subview

Still learning about Objective C and getting the structure right.
I have an iOS App with a UIViewController that has a defined method named "doSomething". In my view controller I have a view and in that view a number of UIButton that I create programmatically (see example below with one button).
Now when I press the button I want to call my method "doSomething". The way I currently do it is like this:
[myButton addTarget:nil
action:#selector(doSomething:)
forControlEvents:UIControlEventTouchUpInside];
Since my target is nil it goes up the responder chain until it finds a method called "doSomething". It works, but it does not really feel right.
I have started to look into using #protocol but not really got my head around it. I have been looking at some tutorials but for me it is not clear enough. I have used protocols like for the table view controllers, but defining one is new for me.
Would it be possible to get an example for this specific case?
Thanks!
As your target pass in the view controller and the method will be called on that object.
Edit:
[myButton addTarget:controller
action:#selector(doSomething:)
forControlEvents:UIControlEventTouchUpInside];
Assuming that you have a variable called controller that is your UIViewController. If you don't have a reference to your controller then simply pass one to your view.
Edit2:
View interface:
#property (assign) UIViewController* controller;
View implementation:
#synthesize controller;
Controller:
- (void) viewDidLoad {
[super viewDidLoad];
someView.controller = self;
}
The way I'd do it is set the value of addTarget to self.
[myButton addTarget:self
action:#selector(doSomething:)
forControlEvents:UIControlEventTouchUpInside]
This will look for the method doSomething on the object that the target is added in. In this case, this would be the view controller.

Objective C Delegate for the Main Application Window

So I'm trying to do this exercise where I need to set a delegate for the main window. The purpose is to make sure that when the user resizes the window, it's always twice as wide as it is high.
This is my AppController.h file:
#import <Cocoa/Cocoa.h>
#interface AppController : NSObject
{
NSWindow *windowWillResize;
}
#end
and this is my AppController.m file:
#import "AppController.h"
#implementation AppController
- (id) init
{
[super init];
windowWillResize = [[NSWindow alloc] init];
[windowWillResize setDelegate:self];
return self;
}
- (NSSize) windowWillResize:(NSWindow *)sender
toSize:(NSSize)frameSize;
{
NSLog(#"size is changing");
return frameSize;
}
#end
However, I can remove the line
[windowWillResize setDelegate:self];
since I set the delegate in Interface Builder, but I'm not sure why this works.
How does windowWillResize know that I'm referring to the main application window since I'm doing a completely new
windowWillResize = [[NSWindow alloc] init];
I have a feeling that I am completely doing this wrong. Could someone point me in the right direction? Thanks!
Indeed, you don't need to create a NSWindow *windowWilResize since a newly created Cocoa app already has a main window. You don't need to implement an -init method either.
You only need to set you appController as a delegate of your main window in Interface Builder and to implement the -windowWillResize: method in your appController.
If you are familiar with french language, you can take a look at a blog entry I have written on this subject: Délégation en Cocoa.
You're leaking an instance of NSWindow. In -init you create an NSWindow instance. However, that is not used because when the NIB loads, it sets up all the connections that you specified in Interface Builder and you start using the window from the NIB instead. Do not create a window object in code - Interface Builder does it for you! :-)
In fact, it's not quite "instead"; your app controller is now the delegate for both NSWindow instances - the one that comes from the NIB and the one you instantiated in -init. However as the in-code NSWindow is never used anywhere else, it's still redundant and should be removed.
If you just want to maintain the aspect ratio of the window you can use either of these two NSWindow methods:
setAspectRatio:(NSSize)
setContentAspectRatio:(NSSize)
The first method locks the entire window size, including the title bar. The second one just the content. You can call this method during the initialization of your window inside the delegate (for example: -applicationDidFinishLaunching)