How to derive multiple classes from a single nib - xcode

I’m trying to create a nib for a generic SectionHeader for a TableView as I need several similar SectionHeaders. I’m trying to follow this SO post:
How to create multiple windows with the same nib file in xcode
The view defined in my nib file is assigned a base class of BaseSectionHeader. Here’s its initializer:
BaseSectionHeader.m
- (id) initWithController:(MasterViewController*)ctlr;
{
self = [[[UINib nibWithNibName:#"SectionHeader" bundle:nil] instantiateWithOwner:ctlr options:nil] objectAtIndex:0];
if (self)
{
_controller = ctlr;
}
return self;
}
Here’s the initializer of a subclass I’d like to derive:
SectionHeader.h
#interface SectionHeader : BaseSectionHeader <UIAlertViewDelegate>
…
#end
SectionHeader.m
- (id) initWithController:(MasterViewController*)ctlr
{
if (self = [super initWithController:ctlr])
{
_deleteConfirmButtonWidth = 70.0;
}
return self;
}
And here’s how I instantiate a section header:
MasterViewController.m
…
SectionHeader* hdr = [[SectionHeader alloc] initWithController:self];
…
The problem is hdr is returned as a BaseSectionHeader, not a SectionHeader. This works correctly if I don't use the nib and construct BaseSectionHeader manually in code. But I’d like to use IB to construct the BaseSectionHeader if I can.
Why is hdr a BaseSectionHeader instead of a SectionHeader when I use a nib? Is there a way use the nib and get the subclass I want for hdr?
FWIW here’s my manual code:
BaseSectionHeader.m
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
_label = [[UILabel alloc] initWithFrame:CGRectMake(10.0, 0.0, 275.0, 40.0)];
[_label setTextColor:TanColor];
[_label setNumberOfLines:2];
[_label setLineBreakMode:NSLineBreakByTruncatingTail];
[_label setAdjustsFontSizeToFitWidth:YES];
[self addSubview:_label];
}
return self;
}
SectionHeader.m
- (id)initWithFrame:(CGRect)frame Controller:(MasterViewController*)ctlr
{
if (self = [super initWithFrame:frame])
{
_controller = ctlr;
_deleteConfirmButtonWidth = 70.0;
_titleButton = [UIButton buttonWithType:UIButtonTypeCustom];
_titleButton.frame = CGRectMake(10.0, 0.0, 275.0, 40.0);
_titleButton.alpha = 0.3;
[self addSubview:_titleButton];
}
return self;
}
Thanks

Typically, a nib is used to instantiate an object whose code stands alone. The nib for objectX is not used within the code of objectX.
When SectionHeader is allocated, and its init called, it passes itself to the BaseSectionHeader version of the same method. But BaseSectionHeader drops that and creates a new object to put in self. Thus a SectionHeader is replaced with a BaseSectionHeader.
The UINib calls to instantiate should be made in MasterViewController.

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.

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! ;=)

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

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

nsdocument subclass variable is somehow reset to nil after a proper initialization: why?

I have a disappearing pointer db; the value is properly set during creation of an NSDocument but at the moment I want to open a sub window, the value has changed to nil! I have the following in an NSDocument subclass:
#interface MW_Document : NSDocument
{
MW_WorkerWindowController *workerController;
__strong MW_db *db;
}
- (IBAction)showWorkerManagementPanel:(id)sender;
//- (IBAction)showSkillManagementPanel:(id)sender;
The implementation contains this:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
if (![self db]) {
db = [[MW_db alloc] init];
NSLog ( #"Debug - Init of db: [%ld]", db ); // never mind the casting problem
}
}
db points at something other than nil, a true address.
Later on, I want to open a window and have this in the implementation of the same NSDocument subclass:
- (IBAction)showWorkerManagementWindow:(id)sender
{
if ( !workerController) {
workerController = [[MW_WorkerWindowController alloc] initWithDb:db];
}
[workerController showWindow:self];
}
I put a break point at the first line, and look at the value of db. It is nil, but I have no idea why. Can anyone explain this to me?
You can implement a lazy accessor:
- (MW_db *)db
{
if (db == nil) {
db = [[MW_db alloc] init];
}
return db;
}
And then use it instead of the ivar:
workerController = [[MW_WorkerWindowController alloc] initWithDb:[self db]];

How to pass an integer to a model class

I'm having a bit of trouble with this one! I have definitely looked in to a lot of posts, but can't get it to work. Previously in my app, I have managed to pass data between classes by assigning it to a property. I'm not sure where I'm going wrong here so any help would be appreciated. I know the following code isn't great, and I'm looking in to dictionaries to help, but for now I would like to get this working.
- (IBAction)checkAnswers:(id)sender
{
// The following if statements check user input and checks to see if it is the right answer.
// The .text parts are uitextfield properties and then shows an image of a tick if correct.
// It then assigns one to a variable and sums them up at the end (blag)
if ([eyepiece.text isEqualToString:#"Eyepiece"]) {
UIImage *image = [UIImage imageNamed:#"Tick.png"];
[eyepieceTick setImage:image];
a = 1;
}
if ([focussingKnobs.text isEqualToString:#"Focussing knobs"]) {
UIImage *image = [UIImage imageNamed:#"Tick.png"];
[focussingTick setImage:image];
b = 1;
}
if ([objectiveLens.text isEqualToString:#"Objective lenses"]) {
UIImage *image = [UIImage imageNamed:#"Tick.png"];
[objectiveTick setImage:image];
c = 1;
}
if ([stage.text isEqualToString:#"Stage"]) {
UIImage *image = [UIImage imageNamed:#"Tick.png"];
[stageTick setImage:image];
d = 1;
}
if ([mirror.text isEqualToString:#"Mirror"]) {
UIImage *image = [UIImage imageNamed:#"Tick.png"];
[mirrorTick setImage:image];
e = 1;
}
blag = a + b + c + d + e;
// Here I update a label with the score
finalScore = [[NSString alloc] initWithFormat:#"%D", blag];
[score setText:finalScore];
// This is probably where I'm going wrong. I'm allocating a model class called
// Level_3_Brain and trying to assign a new property in that class (cellsLevelThree)
// with the score.
// Level_3_Brain *level = [[Level_3_Brain alloc] init];
// level.cellsLevelThree = blag;
// Updated to
[Level_3_Brain sharedInstanceOfLevel3].cellsLevelThree = blag;
// I then set them all back to zero so that the score doesn't go above 5
a = 0, b = 0, c = 0, d = 0, e = 0;
blag = 0;
}
My model class .h now has:
#interface Level_3_Brain : NSObject
+ (id)sharedInstanceOfLevel3;
#property (nonatomic) int cellsLevelThree;
#end
My .m now has this code taken from the Singleton article:
#implementation Level_3_Brain
#synthesize cellsLevelThree;
static Level_3_Brain *sharedInstanceOfLevel3 = nil;
// Get the shared instance and create it if necessary.
+ (Level_3_Brain *)sharedInstanceOfLevel3 {
if (sharedInstanceOfLevel3 == nil)
{
sharedInstanceOfLevel3 = [[super allocWithZone:NULL] init];
}
return sharedInstanceOfLevel3;
}
// We can still have a regular init method, that will get called the first time the Singleton is used.
- (id)init
{
self = [super init];
if (self) {
// Work your initialising magic here as you normally would
}
NSLog(#"%#", cellsLevelThree);
return self;
}
// Your dealloc method will never be called, as the singleton survives for the duration of your app.
// However, I like to include it so I know what memory I'm using (and incase, one day, I convert away from Singleton).
-(void)dealloc
{
// I'm never called!
// [super dealloc];
}
/ We don't want to allocate a new instance, so return the current one.
+ (id)allocWithZone:(NSZone*)zone {
return [self sharedInstanceOfLevel3];
}
// Equally, we don't want to generate multiple copies of the singleton.
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#end
Unfortunately, I'm now getting the error "Property 'cellsLevelThree' not found on object of type id. Please help!!
You correctly identified the place where you are "going wrong" in your comment: the problem is not in that you are setting the value incorrectly, but in that you are setting it on a brand-new instance that is local to the method, and is promptly discarded upon the exit from that method.
In general, your model class(es) should be created when your application starts up, and remain available during the entire lifetime of your application. This is often accomplished using singletons.

Resources