I'm trying to access a NSMutableArray from a different already existing class than it was created in. But if i NSLog it, i get null. My program starts up in class2, then I segue to class1, create my NSMutableArray by pressing one or more rows, and then I want my class2 to get the updated NSMutableArray instance, but all it get is null. Code below:
//class1.m
#import "FocusTagTableViewController.h"
#import "STATableViewController.h"
#implementation FocusTagTableViewController
#synthesize focusArray = _focusArray;
#synthesize allSelectedFocus = _allSelectedFocus;
- (void)viewDidLoad
{
_focusArray = [[NSArray alloc]initWithObjects:#"Balance",#"Bevægelse",#"Elementskift",#"Vejrtrækning",#"Alle",nil];
[super viewDidLoad];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *selectedFocus = [[_focusArray objectAtIndex:indexPath.row] stringByAppendingString:#","];
if(_allSelectedFocus == nil)
{
_allSelectedFocus = [[NSMutableArray alloc]init];
[_allSelectedFocus addObject:selectedFocus];
}
else if(![_allSelectedFocus containsObject:selectedFocus])
{
[_allSelectedFocus addObject:selectedFocus];
}
}
//class2.m
#import "STATableViewController.h"
#import "FocusTagTableViewController.h"
#implementation STATableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
FocusTagTableViewController *focusTag = [[FocusTagTableViewController alloc]init];
[focustag addObserver:self forKeyPath:#"allSelectedFocus" options:NSKeyValueObservingOptionNew context:NULL];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:#"allSelectedFocus"])
{
NSLog(#"%#", [object valueForKeyPath:keyPath]);
}
}
That's because in class 2 you create a new instance of class 1, wich has empty array.
If you want to access that array from class 2, you should make a reference to that first class. Or you could use Key-Value-Observing for that.
Here is the docs about KVO
http://developer.apple.com/library/mac/ipad/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html
In first class, when you create class two, add self as an observer for that array, and implement observeValueForKeyPath: method in class 1
Try this way:
1) Import your class2.h file in your class1 file.
2) Create an NSMutableArray *foo globally in your class2.h file
3) create an object for class2 in your class1 file
4) instead Of using allSelectedFocus in your class1 file, set objects to foo in that file itself like this [class2Object.foo addObject:selectedFocus];
Related
If I have an object that uses KVO to observe a property on some object and then create a partial mock for that observer I no longer receive any notifications. Why is this?
Here's a minimal example:
#interface TestPartialMockAndKVO : SenTestCase
#end
#implementation TestPartialMockAndKVO
- (void)test {
// Should print "Changed!" when foo property is changed
MyObserver* myObserver = [[[MyObserver alloc] init] autorelease];
// But with this line, there is no print out
[OCMockObject partialMockForObject:myObserver];
[myObserver setFoo:#"change"];
}
#end
-
#interface MyObserver : NSObject
#property (copy) NSString* foo;
#end
#implementation MyObserver
- (id)init {
self = [super init];
[self addObserver:self forKeyPath:#"foo" options:0 context:NULL];
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
NSLog(#"Changed!");
}
- (void)dealloc { ... }
#end
Both KVO and OCMock are doing some little runtime tricks whereby they create a private subclass of your actual class in order to perform their magic. KVO is doing a thing called "isa-swizzling", and OCMock is creating an object to be the forwarding target of your original object.
Each system is sort of off in its own little world, with its own class that has nothing to do with the other. Mocking KVO with OCMock looks similar to your problem. I think you should be able to make this work just by telling your mock to
[[myMock expect] observeValueForKeyPath:#"foo"
ofObject:myObserver
change:[OCMArg any]
context:[OCMArg any]];
I have searched a lot but didn't find useful code or tutorial.
In my application, I have an mutable array which update in every 60 seconds.
The objects in array is being displayed by table view in multiple view controllers.
I want to reload table view automatically when only when values in array changes or updated.
For this, I want to add observer on mutable array i.e when values in array changes then it should call a particular method for e.g
-(void)ArrayUpdatedNotification:(NSMutableArray*)array
{
//Reload table or do something
}
Thanks in advance.
You can abstract the array into a data container class with accessor methods, and then use key-value observing to observe when the array that backs the container object is changed (you cannot use KVO on an NSArray directly).
A simple example of a class used as an abstraction on top of an array follows. You use its insertObject:inDataAtIndex: and removeObjectFromDataAtIndex: methods instead of directly accessing the with addObject: and removeObject:.
// DataContainer.h
#interface DataContainer : NSObject
// Convenience accessor
- (NSArray *)currentData;
// For KVC compliance, publicly declared for readability
- (void)insertObject:(id)object inDataAtIndex:(NSUInteger)index;
- (void)removeObjectFromDataAtIndex:(NSUInteger)index;
- (id)objectInDataAtIndex:(NSUInteger)index;
- (NSArray *)dataAtIndexes:(NSIndexSet *)indexes;
- (NSUInteger)countOfData;
#end
// DataContainer.m
#interface DataContainer ()
#property (nonatomic, strong) NSMutableArray *data;
#end
#implementation DataContainer
// We'll use automatic notifications for this example
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:#"data"]) {
return YES;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (id)init
{
self = [super init];
if (self) {
// This is the ivar which provides storage
_data = [NSMutableArray array];
}
return self;
}
// Just a convenience method
- (NSArray *)currentData
{
return [self dataAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self countOfData])]];
}
// These methods enable KVC compliance
- (void)insertObject:(id)object inDataAtIndex:(NSUInteger)index
{
self.data[index] = object;
}
- (void)removeObjectFromDataAtIndex:(NSUInteger)index
{
[self.data removeObjectAtIndex:index];
}
- (id)objectInDataAtIndex:(NSUInteger)index
{
return self.data[index];
}
- (NSArray *)dataAtIndexes:(NSIndexSet *)indexes
{
return [self.data objectsAtIndexes:indexes];
}
- (NSUInteger)countOfData
{
return [self.data count];
}
#end
The reason that we do this is so we can now observe changes made to the underlying array. This is done through Key Value Observing. A simple view controller that instantiates and observes a data controller is shown:
// ViewController.h
#interface ViewController : UIViewController
#end
// ViewController.m
#interface ViewController ()
#property (nonatomic,strong) DataContainer *dataContainer;
#end
#implementation ViewController
static char MyObservationContext;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Instantiate a DataContainer and store it in our property
_dataContainer = [[DataContainer alloc] init];
// Add self as an observer. The context is used to verify that code from this class (and not its superclass) started observing.
[_dataContainer addObserver:self
forKeyPath:#"data"
options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
context:&MyObservationContext];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Check if our class, rather than superclass or someone else, added as observer
if (context == &MyObservationContext) {
// Check that the key path is what we want
if ([keyPath isEqualToString:#"data"]) {
// Verify we're observing the correct object
if (object == self.dataContainer) {
NSLog(#"KVO for our container property, change dictionary is %#", change);
}
}
}
else {
// Otherwise, call up to superclass implementation
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Insert and remove some objects. Console messages should be logged.
[self.dataContainer insertObject:[NSObject new] inDataAtIndex:0];
[self.dataContainer insertObject:[NSObject new] inDataAtIndex:1];
[self.dataContainer removeObjectFromDataAtIndex:0];
}
- (void)dealloc
{
[_dataContainer removeObserver:self forKeyPath:#"data" context:&MyObservationContext];
}
#end
When this code runs, three changes to the data are observed by the view controller and logged to the console:
KVO for our container property, change dictionary is {
indexes = "<NSIndexSet: 0x8557d40>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
kind = 2;
new = (
"<NSObject: 0x8557d10>"
);
}
KVO for our container property, change dictionary is {
indexes = "<NSIndexSet: 0x715d2b0>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
kind = 2;
new = (
"<NSObject: 0x71900c0>"
);
}
KVO for our container property, change dictionary is {
indexes = "<NSIndexSet: 0x8557d40>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
kind = 3;
old = (
"<NSObject: 0x8557d10>"
);
}
While this is somewhat complex (and can get much more involved), this is the only way to be notified automatically that a mutable array's contents were changed.
What is can do is - After updating your Array send a Notification (NSNotificationCenter) and this notification will be received by all the controllers. On receiving the notificaiton the controller should do [tableview reloaddata].
Code example:
// Adding an observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateTable:) name:#"arrayUpdated" object:nil];
// Post a notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"arrayUpdated" object:nil];
// the void function, specified in the same class where the Notification addObserver method has defined
- (void)updateTable:(NSNotification *)note {
[tableView reloadData];
}
If you want to use shiny blocks you can do this
// Create an instance variable for your block holder in your interface extension
#property (strong) id notificationHolder;
// Listen for notification events (In your TableView class.
self.notificationHolder = [[NSNotificationCenter defaultCenter] addObserverForName:#"NotificationName"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
NSLog(#"Received notification");
}];
Then in dealloc (or when you don't use it anymore)
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self.notificationHolder];
}
Then in some other class
// Send a notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationName" object:nil];
Ask if something is not clear! Hope it helps!
EDIT DUE TO COMMENT
The "YourEvent" is the name of the notification, this means that you can name it to whatever you want. (Perhaps "UpdateArrayNotification could be a good name?)
Something to think about: Note that you can have several observers for the same notification. This means that one 'post' will be snapped up by all observers.
I have a View-Based NSTableView which should be initially empty. I also have "add" and "remove" buttons for adding and deleting rows from same NSTableView.
My delegate methods and method for adding new row look like this:
#import "PreferencesApiKeysViewController.h"
#import "UserKeys.h"
#interface PreferencesApiKeysViewController ()
#property (weak) IBOutlet NSTableView *keysTableView;
#end
#implementation PreferencesApiKeysViewController
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// Get a new ViewCell
NSTableCellView *cellView = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
if([tableColumn.identifier isEqualToString:#"userKeysColumn"]) {
UserKeys *allKeys = [self.allKeys objectAtIndex:row];
cellView.textField.stringValue = allKeys.userKeyName;
return cellView;
}
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
NSLog(#"Initial rows: %li", (unsigned long)[self.allKeys count]);
return [self.allKeys count];
}
- (IBAction)addKey:(id)sender {
UserKeys *newKey = [[UserKeys alloc] initWithKeyName:#""
apiID:#""
apiCode:#"" ];
[self.allKeys addObject:newKey];
NSLog(#"Total rows: %li", (unsigned long)self.allKeys.count);
NSInteger newRowIndex = self.allKeys.count;
if (newRowIndex == 0) {
NSLog(#"No rows.");
newRowIndex = 0;
} else {
NSLog(#"Has rows.");
newRowIndex = self.allKeys.count-1;
}
NSLog(#"New Index: %ld", (long)newRowIndex);
[self.keysTableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:newRowIndex] withAnimation:NSTableViewAnimationSlideDown];
[self.keysTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRowIndex] byExtendingSelection:NO];
[self.keysTableView scrollRowToVisible:newRowIndex];
}
#end
and in my AppDelegate.m, I'm calling my view like this:
#import "AppDelegate.h"
#import "UserKeys.h"
#include "PreferencesApiKeysViewController.h"
#interface AppDelegate()
#property (nonatomic,strong) IBOutlet PreferencesApiKeysViewController *prefsViewController;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.prefsViewController = [[PreferencesApiKeysViewController alloc] initWithNibName:#"PreferencesApiKeysViewController" bundle:nil];
// Create few rows as dummy data
/*
UserKeys *key1 = [[UserKeys alloc] initWithKeyName:#"key one"
apiID:#"123"
apiCode:#"xxx" ];
UserKeys *key2 = [[UserKeys alloc] initWithKeyName:#"key two"
apiID:#"456"
apiCode:#"yyy" ];
NSMutableArray *tempKeys = [NSMutableArray arrayWithObjects:key1, key2, nil];
self.prefsViewController.allKeys = tempKeys;
*/
// done.
[self.window.contentView addSubview:self.prefsViewController.view];
self.prefsViewController.view.frame = ((NSView*)self.window.contentView).bounds;
}
#end
Now, the thing here is that if I uncomment those few lines for adding dummy data in AppDelegate and launch my app, everything works fine. I can add/remove rows without any problem, I can even delete all of them and add a new one after that.
But, if I comment those lines again, my app starts with an empty table (which is what I need), and when I want to add new row I get an error:
*** Assertion failure in -[NSTextFieldCell _objectValue:forString:errorDescription:], /SourceCache/AppKit/AppKit-1187.34/AppKit.subproj/NSCell.m
Looking further the thread, I see only one line referencing to my app:
0x0000000100003116 -[PreferencesApiKeysViewController tableView:viewForTableColumn:row:] + 406
I'm guessing that my TableView is not properly instantiated if allKeys array doesn't contain any single object really, but I'm not sure how to fix that? How to create an empty NSTableView and have the ability to add the first row by myself when "add" button is clicked (without adding any dummy data to allKeys)?
Add this to the implementation of PreferencesApiKeysViewController:
#implementation PreferencesApiKeysViewController
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
self.allKeys = [NSMutableArray array];
}
return self;
}
// add the following only if not using ARC
- (void)dealloc {
[_allKeys release];
[super dealloc];
}
#end
What's happening when you allow that commented-out code to be compiled in is that you assign an NSMutableArray instance to the allKeys instance variable. As a result, adding and removing items works as you would expect.
Generally, in a case like this, the class that manages the NSMutableArray should override the init method to make sure the array is properly initialized to an empty array. (By default, non-IBOutlet instance variables are initialized to nil, which won't allow you to properly add or remove items from it).
I am trying to set the value of an NSTextField, but it's not working properly.
I have a button linked to an IBAction, and when I set it using self, it works fine:
#import <Foundation/Foundation.h>
#interface TestMessage : NSObject {
IBOutlet NSTextField *text;
}
- (IBAction) setMessage: (id) controller;
- (void) Message:(NSString *) myMessage;
#end
#import "TestMessage.h"
#implementation TestMessage
- (IBAction) setMessage: (id) controller {
// This works
[self Message:#"Hello"];
// but this doesn't
TestMessage * messageTest= [TestMessage new];
[messageTest Message:#"Hi"];
}
- (void) Message: (NSString *) myMessage {
[text setStringValue: myMessage];
NSLog(#"Message Was Called");
// This returns <NSTextField: 0x1001355b0> when called
// using self, but null when called the other way.
NSLog(#"%#", text);
}
#end
I've searched for a while, but still can't find the answer.
I guess it has something to do with the delegate, but I'm not sure.
Thanks in advance.
Are you sure message is called when you call it from anotherFuntion? If anotherFuntion is a method of another class, calling [self message:] won't work as you expected to...
I know this is an old post, but I have been fiddling with the same issue today.
You have to return string value in textfield:
[textField stringValue];
The code
TestMessage * messageTest = [TestMessage new];
is unusual, specifically new. I'm going to assume that new is just a class method does normal alloc/init equivalent to
TestMessage * messageTest = [[TestMessage alloc] init];
The main problem is that IBOutlet NSTextField *text will be initialized only if the class TestMessage is loaded with a Nib file. It would have to be named as the class of an object in Interface Builder, like so
and you would have to implement initWithCoder and encodeWithCoder something like this in order to extract your field value from the IB encoding:
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
self.text = [coder decodeObjectForKey:#"text"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)coder
{
[super encodeWithCoder:coder];
[coder encodeObject:self.text forKey:#"text"];
}
Fundamentally, IBOutlet fields do not get wired up wherever you create an instance of that class. If they did, how would you express that field A should be wired to UI object A and field B should be wired to UI object B? The connection is established only in the context of loading a class from a Nib file.
Simple question: where do the tableView and section arguments get passed from? The actual code in the method return [self.listData count]; doesn't even mention them.
Here's my interface code:
#interface Simple_TableViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource>
{
NSArray *listData;
}
#property (nonatomic, retain) NSArray *listData;
#end
And this is all the implementation code:
#import "Simple_TableViewController.h"
#implementation Simple_TableViewController
#synthesize listData;
- (void)viewDidLoad {
NSArray *array = [[NSArray alloc] initWithObjects:#"Sleepy", #"Sneezy",
#"Bashful", #"Happy", #"Doc", #"Grumpy", #"Dopey", #"Thorin",
#"Dorin", #"Nori", #"Ori", #"Balin", #"Dwalin", #"Fili", #"Kili",
#"Oin", #"Gloin", #"Bifur", #"Bofur", #"Bombur", nil];
self.listData = array;
[array release];
[super viewDidLoad];
}
- (void)viewDidUnload {
self.listData = nil;
}
- (void)dealloc {
[listData release];
[super dealloc];
}
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.listData count];
}
I just want to know how does the method (NSInteger)tableView: (UITableView *)numberOfRowsInSection: receive those arguments? Of course this happens everywhere; I just want to understand it.
The Simple_TableViewController class is likely meant to manage a single table with a single section. Given that, the tableView and section parameters aren't important because they can only be one thing: a pointer to the table and 0, respectively.
Your view controller class is adding support for these callback methods through UITableViewDelegate and UITableViewDataSource. You are adding this support in your .h file through <UITableViewDelegate, UITableViewDataSource>. These classes are built in to the Cocoa Touch framework and you are just using them. When the table is (re)loaded, this callback methods are called if you have defined them (some are required, others are optional).