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

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.

Related

NSCollectionView not updating when adding objects

I have an NSCollectionView with an array controller that is successfully showing objects after they are added and the application is restarted, but not when immediately when added.
I have a controller class which is a subclass of NSObject that reads data from a plist into an NSMutableArray, which is bound to an Array Controller, which is in turn bound to my NSCollectionView. I believe my bindings are correct, as if I add an object, after I restart my application, everything shows up fine, including the new object and all the bound attributes. But when I add an object, it won't be added immediately. The application needs to be restarted. I believe that since my bindings appear to be correct, this is an issue with my controller class not being Key-Value compliant.
I have implemented all of the methods I believe I should have, as per the "Key-Value Coding Accessor Methods" section of the Key-Value Coding programming guide. I believe I have implemented each of the required accessors in the [Collection Accessor Patterns for To-Many Properties][1] section. Furthermore, in the [Quick Start for Collection Views][2], which I have completed, it states not even all of these methods need to be implemented (which I have confirmed).
Here are some code samples to better explain what I am doing.
My collection class, "MYCollection":
#import <Foundation/Foundation.h>
#import "MyObject.h"
#interface MYCollection : NSObject
#property (retain, readwrite) NSMutableArray* objects;
- (void)insertObject:(MYObject *)object inObjectsAtIndex:(NSUInteger)index;
#end
#import "MYObjectCollection.h"
#import "MYObject.h"
#implementation MYObjectCollection
#synthesize objects = _objects;
- (id)init {
self = [super init];
if (self) {
_objects = [self objects];
}
return self;
}
- (NSArray*)objects {
// here I retrieve the objects from the plist into a mutable array
// let's call that array "sortedArray"
return sortedArray;
}
- (void)setObjects:(NSMutableArray *)objectsArray {
// here I write the object array to a plist
_objects = objectsArray;
}
-(void)insertObject:(MYObject*)object inObjectsAtIndex:(NSUInteger)index {
[_objects insertObject:object atIndex:index];
[self setObjects:_objects];
return;
}
-(void)addObjectsObject:(MYObject*)object {
[_objects addObject:object];
[self setObjects:_objects];
return;
}
-(void)removeObjectFromObjectsAtIndex:(NSUInteger)index {
[_objects removeObjectAtIndex:index];
[self setObjects:_objects];
return;
}
-(void)removeObjectsObject:(MYObject*)object {
[_objects removeObject:object];
[self setObjects:_objects];
return;
}
-(id)objectInObjectsAtIndex:(NSUInteger)index {
return [_objects objectAtIndex:index];
}
-(NSUInteger)countOfObjects {
return [_objects count];
}
- (NSEnumerator *)enumeratorOfObjects {
return [_objects objectEnumerator];
}
#end
I am adding objects to this controller by means of an external view, elsewhere:
MYObjectCollection *collection = [[MYObjectCollection alloc] init];
[collection insertObject:new inObjectsAtIndex:[collection.objects count]];
I'm not sure how to continue troubleshooting this issue. I believe that my bindings are correct and I think I have implemented all of the necessary methods for Key-Value coding, but maybe I haven't, or maybe they're wrong. Any help would be appreciated.

Cocoa: ABPeoplePickerView - How can I trigger a Method upon single clicking a person?

I've looked everywhere, and hope that perhaps someone can point me in the right direction.
I just want to run a method each time the user selects a different record.
The bigger picture (in case there is an alternate way) is that when the user selects the record (single click) the persons phone numbers are to be put into a segmented control.
I've tried:
To connect an action to a button, I usually open the assistant editor, and right-click drag to the .h file. But when I'm doing it with this abpeoplepickerview I only get an Outlet connection type?
the people picker is a . 'compound view' that actually consits of a tableview, 2 buttons and a searchfield (IIRC)
answer:
you're out of luck and this component isnt suitable for you BUT of course you do some hacking:
- (void)viewDidLoad {
//you get the internal tableview
id views = [self findSubviewsOfKind:NSClassFromString(#"ABPeoplePickerTableView") withTag:NSNotFound inView:sef.peoplePickerView];
id view = [views count] ? [views objectAtIndex:0] : nil;
//subscribe to the notification
if([view respondsToSelector:#selector(selectedRow)]) {
[[NSNotificationCenter defaultCenter] addObserverForName:NSTableViewSelectionDidChangeNotification object:view queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self peoplePickerSelectedRecordChanged:self.peoplePickerView];
}];
}
}
- (NSArray *)findSubviewsOfKind:(Class)kind withTag:(NSInteger)tag inView:(NSView*)v {
NSMutableArray *array = [NSMutableArray array];
if(kind==nil || [v isKindOfClass:kind]) {
if(tag==NSNotFound || v.tag==tag) {
[array addObject:v];
}
}
for (id subview in v.subviews) {
NSArray *vChild = [self findSubviewsOfKind:kind withTag:tag inView:subview];
[array addObjectsFromArray:vChild];
}
return array;
}
- (IBAction)peoplePickerSelectedRecordChanged:(id)sender {
NSLog(#"%#", [sender selectedRecords]);
}
ABPeoplePickerView gives notifications for exactly what you need. Look near the end of the class reference.
#implementation someController
#synthesize picker; //your ABPeoplePickerView
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
// or some other method that gets called early on
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(notificate:)
name:ABPeoplePickerNameSelectionDidChangeNotification
object:picker];
}
- (void) notificate: (NSNotification*) notification {
ABPerson *person = picker.selectedRecords.firstObject;
NSLog(#"NOTIFIED %#"), person.name);
// name is a property of ABPerson I added in a category
// do what you will
}
Don't forget to remove the observer if you dismiss the window.

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

Why do I get different results when I run a program from testing trough Xcode and just taping the icon on the device?

I am having a really weird problem because i get completely different results between testing my program WHILE connected to the computer (trough xcode) but ON my device. and just taping the icon while not being plugged to xcode. (I think it might be coordinate issues).
So i was thinking there might be a difference between testing in these 2 ways.
Sorry i forgot to specify, I used to get the same results in both ways but then i created a singleton for my location manager instead of creating a single location manager object in each window.
This is how i am creating the Header:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
// protocol for sending location updates to another view controller
#protocol LocationManagerDelegate <NSObject>
#required
- (void)locationUpdate:(CLLocation*)location;
#end
#interface LocationManagerSingleton : NSObject <CLLocationManagerDelegate> {
CLLocationManager* locationManager;
CLLocation* location;
//id delegate;
}
#property (nonatomic, retain) CLLocationManager* locationManager;
#property (nonatomic, retain) CLLocation* location;
#property (nonatomic, assign) id <LocationManagerDelegate> delegate;
+ (LocationManagerSingleton*) sharedInstance; // Singleton method
#end
and this is the implementation:
#import "LocationManagerSingleton.h"
//static LocationManagerSingleton* sharedCLDelegate = nil;
#implementation LocationManagerSingleton
#synthesize locationManager, location, delegate;
#pragma mark - Singleton Methods -
+ (LocationManagerSingleton*)sharedInstance {
static LocationManagerSingleton *_sharedInstance;
if(!_sharedInstance) {
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[super allocWithZone:nil] init];
});
}
return _sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#if (!__has_feature(objc_arc))
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
#endif
#pragma mark - Custom Methods -
// Add your custom methods here
- (id)init
{
self = [super init];
if (self != nil) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.distanceFilter = 5;
self.locationManager.purpose = #"This app uses your location for Augmented Reality";
[self.locationManager startUpdatingLocation];
[self.locationManager startUpdatingHeading];
NSLog(#"LocationManager initialized with accuracy best for Navigation");
NSLog(#"CUrrent Latitude: %f, Current Longitude: %f",locationManager.location.coordinate.latitude,locationManager.location.coordinate.longitude);
}
return self;
}
#pragma mark - CLLocationManagerDelegate Methods -
- (void)locationManager:(CLLocationManager*)manager
didUpdateToLocation:(CLLocation*)newLocation
fromLocation:(CLLocation*)oldLocation
{
/*…some filer method to check if the new location is good …*/
bool good = YES;
if (good)
{
[self.delegate locationUpdate:newLocation];
}
//self.location = newLocation;
//NSLog(#"Updated: %#",newLocation);
}
- (void)locationManager:(CLLocationManager*)manager
didFailWithError:(NSError*)error
{
/* ... */
}
#end
Okay it seems that OpenGL was causing the problem. My theory was that since a pointer variable used for texturing was inside a loop when the localization manager updated and redraw this variable would get messed up because it was being reinitialized everyrun but the value wouldnt be set to 0, and since opengl has the pointer to the adress not to the pointer it would read corrupted data (since the loop might have updated that space until opengl was told the new adress of the variable). Still i have no idea why it worked perfectly while hooked up to the computer and not by itself.

NSWindowController subClass - Init is Called twice

im very new in cocoa development and I'm trying to load a Window.
I will explain my problem.
When the user click the menuItem I use the following code to load my window
if ( !cadastroContasController )
{
cadastroContasController = [[cadastroContas alloc]init];
[cadastroContasController SetMenuItem:sender];
}
if ( ![[cadastroContasController window] isVisible] )
{
NSLog(#"!isVisible");
[cadastroContasController showWindow:nil];
}
I my cadastroContas class looks like this:
#interface cadastroContas : NSWindowController
{
NSMenuItem *mnuCommand;
IBOutlet NSComboBox *cmbSelecao;
IBOutlet NSTextField *txtNome;
IBOutlet NSTextField *txtSaldoInicial;
IBOutlet NSTextField *txtAnotacoes;
}
- (void)windowDidBecomeKey:(NSNotification *)notification;
- (BOOL)windowShouldClose:(id)sender;
- (void)windowWillClose:(NSNotification *)notification;
- (void)SetMenuItem:(NSMenuItem*) menu;
- (NSMenuItem*) MenuItem;
#end
and the implementation is
#implementation cadastroContas
-(void)windowDidLoad
{
NSLog(#"windowDidLoad");
[mnuCommand setState:NSOnState];
}
-(id)init
{
self = [super initWithWindowNibName:#"cadastroContas"];
NSLog(#"Init self=%p", self);
return self;
}
-(void)dealloc
{
NSLog(#"Dealoc=%p", self);
[super dealloc];
}
- (void)windowDidBecomeKey:(NSNotification *)notification
{
NSLog(#"windowDidBecomeKey window=%p", [self window]);
}
- (BOOL)windowShouldClose:(id)sender
{
NSLog(#"windowShouldClose Window=%p", [self window]);
NSLog(#"mnuComando=%p GetMenuItem=%p", mnuCommand, [self MenuItem] );
if ( mnuCommand )
{
[mnuCommand setState:NSOffState];
}
return YES;
}
- (void)windowWillClose:(NSNotification *)notification
{
NSLog(#"windowWillClose Window=%p", [self window]);
NSLog(#"mnuCommand=%p GetMenuItem=%p", mnuCommand, [self MenuItem] );
[self dealloc];
}
- (void)SetMenuItem:(NSMenuItem*) menu
{
mnuCommand = menu;
}
- (NSMenuItem*) MenuItem
{
return mnuCommand;
}
#end
When the menu was clicked, I received two messages "Init" and I don't know why.
Exemple:
[2223:a0f] Init self=0x10014fe40
[2223:a0f] Init self=0x10011f5a0
The second message let the "[cadastroContasController SetMenuItem:sender];" useless.
So, I need help to understand whats going on..
Another thing, [[cadastroContasController window] is always returning NULL(0x0)!!, but inside my controller i can handle it (it isn't null).
This means you inited two instances, as shown by your logging of the self pointer: Notice that the value is different between the two messages.
You can use the Allocations instrument in Instruments to see what caused each window controller to be instantiated.
Usually, this problem happens when you create one of these in the nib and the other one in code. In the case of a window controller, the one you create in code should be the owner of its nib; you should not create another window controller as an object in the nib.
Another thing, [[cadastroContasController window] is always returning NULL(0x0)!!, but inside my controller i can handle it (it isn't null).
The window controller whose window outlet you set to the window is the one that is returning non-nil. The window controller whose window outlet you didn't set is the one that is returning nil.
Following from what I said above, after deleting the window controller you created in the nib, you should connect your File's Owner's window outlet to the window.

Resources