Retaining array in subclass - xcode

Updated:
I have subclassed UIImageView to make some images movable through touch gestures. In the view controller I have an array holding the initial position of each imageview. My problem is that this array returns null whenever it is called on from the subclass. The idea is to check if the image is in its original position or if it has already been moved before, I have stripped the code to just NSLog what's going on but the problem remains and is driving me nuts.
ViewController.h
NSMutableArray *originalPositions;
#property (nonatomic, retain) NSMutableArray *originalPositions;
-(void)testLogging;
ViewController.m
#synthesize originalPositions;
- (void)viewDidLoad {
originalPositions = [[NSMutableArray alloc]init];
}
-(void)drawImages {
for (int i = 0; i < imagesArray.count; i++) {
CGRect frame = CGRectMake(65 * i, 10, 60, 60);
draggableView *dragImage = [[draggableView alloc] initWithFrame:frame];
NSString* imgName = [imagesArray objectAtIndex:i];
[dragImage setImage:[UIImage imageNamed:imgName]];
[dragImage setUserInteractionEnabled:YES];
[self.view addSubview:dragImage];
NSString *originalPositionAsString = NSStringFromCGPoint(dragImage.center);
[originalPositions addObject:originalPositionAsString];
}
}
-(void)testLogging {
NSLog(#"Logging array: %#", originalPositions);
}
-(IBAction)btnClicked {
[self testLogging];
}
When called from the IBAction (or any other way) in the class, the 'testLogging'-method NSLogs the array correctly but if I call the same method from the subclass, it NSLogs null:
Subclass.m
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
ViewController *viewController = [[ViewController alloc] init];
[viewController testLogging];
}

self.originalPositions = [[NSMutableArray alloc]init];
This already is a memory leak, when you call self.originalPositions = bla then 'bla' will be retained. Thus meaning that the retain count is upped. This means that your mutable array will have a retain count of 2 and that it will leak once your UIImageView is gone.
As for the rest, I wouldn't know what is wrong with your code. My first guess would be that you didn't call populatePositionsArray yet, you should call this whenever your view is created / shown so you are sure the array is populated when touchesBegan is called.
Alternatively you could include an 'if' statement in the touchesBegan that checks whether the array exists and otherwise call the populatePositionsArray to populate it before continueing.

I solved it by writing the array to a plist file after populating it. Not very elegant but working.
Thanks for the help though guys.

Related

Populating NSOutlineView with bindings - KVO adding of items

I've created a small test project to play with NSOutlineView before using it in one of my projects. I'm successful in displaying a list of items with children using a NSTreeController who's content is bound to an Array.
Now while I created this object it took me ages to realize that my array contents would only show up if i created them in my init method:
- (id)init
{
self = [super init];
if (self) {
results = [NSMutableArray new];
NSMutableArray *collection = [[NSMutableArray alloc] init];
// Insert code here to initialize your application
NSMutableDictionary *aDict = [[NSMutableDictionary alloc] init];
[aDict setValue:#"Activities" forKey:#"name"];
NSMutableArray *anArray = [NSMutableArray new];
for (int i; i<=3 ; i++) {
NSMutableDictionary *dict = [NSMutableDictionary new];
[dict setValue:[NSString stringWithFormat:#"Activity %d", i] forKeyPath:#"name"];
[anArray addObject:dict];
}
results = collection;
}
return self;
}
If I put the same code in applicationDidFinishLaunching it wouldn't show the items.
I'm facing the same issue now when trying to add items to the view. My understanding of using the NSTreeController is that it handles the content similar to what NSArrayController does for a NSTableView (OutlineView being a subclass and all). However, whenever I use a KV compliant method to add items to the array the items do not show up in my view.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSMutableDictionary *cDict = [[NSMutableDictionary alloc] init];
[cDict setValue:#"Calls" forKey:#"name"];
[results addObject:cDict];
[outlineView reloadData];
}
I've also tried calling reloadData on the outlineview after adding an object, but that doesn't seem to be called. What am I missing?
Here's a link to my project: https://dl.dropboxusercontent.com/u/5057512/Outline.zip
After finding this answer:
Correct way to get rearrangeObjects sent to an NSTreeController after changes to nodes in the tree?
It turns out that NSTreeController reacts to performSelector:#selector(rearrangeObjects) withObject:afterDelay:
and calling this after adding the objects lets the new objects appear.

IOS 7 - Update an UILabel with value computed in background using delegation

I am having a problem trying to update UILabel.text using a value calculate in background using delegation (which seems to be slow).
#interface BTAViewController : UIViewController <ExchangeFetcherManagerDelegate>
#property (weak, nonatomic) IBOutlet UILabel *bidPriceLabel;
#property (strong, nonatomic) FetcherManager *fetcherManager;
#end
my BTAViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.fetcherManager = [[FetcherManager alloc]init];
double calculateValue = [self.fetcherManager fetchPricesFor];
self.bidPriceLabel.contentMode = UIViewContentModeRedraw;
[self.bidPriceLabel setNeedsDisplay];
self.bidPriceLabel.text = [[NSString alloc] initWithFormat:#"%f", calculateValue];
}];
The label has been correctly initialised in a method not shown here and it shows the correct default value at startup. Once I execute this code, the label will get the value 0.00000000, because my calculateValue has not been calculate yet.
How can I wait for this value to be calculated and then display it has text of the label ?
I can't post images because I don't have enough reputation...
Thanks
Are you sure the value calculated is not purely 0 or null?
What I would do is to add a delegate callback function in BTAViewController.m from FetchManager
Something like this:
BTAViewController.h
//add delegate callback function under #interface
-(void)valueReturned:(float)someValue;
BTAViewController.m
-(void)valueReturned:(float)someValue{
//updates value when the delegate returns the value
self.bidPriceLabel.text = [[NSString alloc] initWithFormat:#"%f", calculateValue];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.fetcherManager = [[FetcherManager alloc]init];
self.fetchManager.delegate = (id)self;
[self.fetchManager getPricesFor];
// double calculateValue = [self.fetcherManager fetchPricesFor];
self.bidPriceLabel.contentMode = UIViewContentModeRedraw;
[self.bidPriceLabel setNeedsDisplay];
[self.view addSubview:self.bidPriceLabel];
}];
In FetchManager.h and .m
FetcherManager.h
#property (retain,nonatomic)id<ExchangeFetcherManagerDeelgate>delegate;
-(void)getPricesFor;
FetcherManager.m
#synthesize delegate;
-(void)getPricesFor{
float price;
//calculate price
[self.delegate valueReturned:price];
}
Add the label to the view, set it to be hidden (bidPriceLabel.hidden = YES). Then, create and add a method for the delegate (BTAViewController) which when fired, checks the hidden property of the UILabel, and if it is TRUE (is hidden), set bidPriceLabel.hidden = NO which will then display the UILabel as you intended to.
Call this method in FetchManager once you are done retrieving the information.

What does OSX do when I customize an NSTableView cell?

I am trying to customize an NSImageCell for NSTableView using NSArrayController and bindings to change the background of the cell which is selected. So, I created two NSImage images and retain them as normalImage and activeImage in the cell instance, which means I should release these two images when the cell calls its dealloc method. And I override
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
and
- (void) setObjectValue:(id) inObject
But I find that when I click any cell in the tableview, the cell's dealloc method is called.
So I put NSLog(#"%#", self); in the dealloc method and - (void)drawInteriorWithFrame:inView: and I find that these two instance are not same.
Can anyone tell me why dealloc is called every time I click any cell? Why are these two instances not the same? What does OS X do when I customize the cell in NSTableView?
BTW: I found that the -init is called only once. Why?
EDIT:
My cell code
#implementation SETableCell {
NSImage *_bgNormal;
NSImage *_bgActive;
NSString *_currentString;
}
- (id)init {
if (self = [super init]) {
NSLog(#"setup: %#", self);
_bgNormal = [[NSImage imageNamed:#"bg_normal"] retain];
_bgActive = [[NSImage imageNamed:#"bg_active"] retain];
}
return self;
}
- (void)dealloc {
// [_bgActive release]; _bgActive = nil;
// [_bgNormal release]; _bgNormal = nil;
// [_currentString release]; _currentString = nil;
NSLog(#"dealloc: %#", self);
[super dealloc];
}
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSLog(#"draw: %#", self);
NSPoint point = cellFrame.origin;
NSImage *bgImg = self.isHighlighted ? _bgActive : _bgNormal;
[bgImg drawAtPoint:p fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
NSPoint strPoint = cellFrame.origin;
strPoint.x += 30;
strPoint.y += 30;
[_currentString drawAtPoint:strPoint withAttributes:nil];
}
- (void) setObjectValue:(id) inObject {
if (inObject != nil && ![inObject isEqualTo:_currentString]) {
[self setCurrentInfo:inObject];
}
}
- (void)setCurrentInfo:(NSString *)info {
if (_currentString != info) {
[_currentString release];
_currentString = [info copy];
}
}
#end
As a normal recommendation, you should move to ARC as it takes cares of most of the memory management tasks that you do manually, like retain, releases. My answers will assume that you are using manual memory management:
Can anyone tell me why dealloc is called every time I click any cell ?
The only way for this to happen, is if you are releasing or auto-releasing your cell. If you are re-using cells, they shouldn't be deallocated.
Why these tow instance are not same ?
If you are re-using them, the cell that you clicked, and the cell that has been deallocated, they should be different. Pay close attention to both your questions, in one you assume that you are releasing the same cell when you click on it, on the second you are seeing that they are different.
What does Apple do when I custom the cell in NSTableView ?
Apple as a company? Or Apple as in the native frameworks you are using? I am assuming you are going for the second one: a custom cell is just a subclass of something that the NSTableView is expecting, it should behave the same as a normal one plus your custom implementation.
BTW: I found that the init is called only once, and why ?
Based on this, you are probably re-using cells, and only in the beginning they are actually being initialised.
It would be very useful to see some parts of your code:
Your Cell's code
Your NSTableView cell's creation code.

How to set a value for an image

I am using a switch statement to fill imageviews with pictures at random.
Now I am trying to give the pictures a value. That way I can use that value to make comparison. Can somebody explain me how to give images a value?
I have used the following code to setup the array for the UIImages, but can't get it completely working: (.m file)
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIImage *image1 = [UIImage imageNamed:#"image-1.png"];
UIImage *image2 = [UIImage imageNamed:#"image-2.png"];
UIImage *image3 = [UIImage imageNamed:#"image-3.png"];
UIImage *image4 = [UIImage imageNamed:#"image-4.png"];
UIImage *image5 = [UIImage imageNamed:#"image-5.png"];
_imageArray = #[image1, image2, image3, image4, image5];
}
-(void)displayImageRandomlyOnView {
NSUInteger randomIndex = arc4random_uniform([_imageArray count]);
_imageToDisplay.image = [_imageArray objectAtIndex:randomIndex];
}
Have created also a button in the .h file, to call the view...but can't get the code working for the button calling the view.
If I now try to run the IS it crashes even without the button in it. Can somebody tell me what is going wrong?
The following information is in the .h file: (have left the button out)
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIImageView *imageToDisplay;
#property (strong, nonatomic) NSArray *imageArray;
That was really helpful! I have now created a button that will fill in total 10 UiImage views. 5 in the left side of the screen, UiImage view 1-5 and right side I have UiImage view 6-10. I created two separe array's to fill the right and the left side. The only thing I now want to do is to compare the UiImage view 1-5 against 6-10. If a UiImage view is filled with an image it should return value 1 and if not it should have 0. This way I can compare if the right or left side has more images filled or exactly the same. Can you point me in the right direction on how to do this? Thanks!
I would suggest storing the UIImages in an array, sorting them randomly, and then sequentially fill the UIImageViews with the images. This way you don't need to attach a value to the images as you know what position they are in the array from their position amongst your image views.
EDIT
You should replace the line
_imageArray = #[image1, image2, image3, image4, image5];
with
_imageArray = [[NSArray alloc] initWithObjects:image1, image2, image3, image4, image5, nil];
What you had would cause an error like this in the Xcode console:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]'
EDIT 26/11/13
If you have a list of images on the screen and want to check programatically if they are loaded with images, I would suggest using an array of UIImageViews instead of an array of UIImages. Then,
// Initialise the 5 UIImageViews with the UIImages that you already initialised.
UIImageView *image1 = [[UIImageView alloc] initWithImage:image1];
UIImageView *image2 = [[UIImageView alloc] initWithImage:image2];
..
..
UIImageView *image5 = [[UIImageView alloc] initWithImage:image1];
// Put all the UIImageViews in an array.
_imageArray = [[NSArray alloc] initWithObjects:image1, image2, image3, image4, image5, nil];
// Make this method to count the number of images in the given array.
- (int)numberOfImagesIn:(NSArray *)array {
int count = 0;
for (UIImageView *view in array) {
if (view.image) {
count++;
}
}
return count;
}

KVC/KVO and bindings: why am I only receiving one change notification?

I'm seeing some quirky behaviour with Cocoa's KVC/KVO and bindings. I have an NSArrayController object, with its 'content' bound to an NSMutableArray, and I have a controller registered as an observer of the arrangedObjects property on the NSArrayController. With this setup, I expect to receive a KVO notification every time the array is modified. However, it appears that the KVO notification is only sent once; the very first time the array is modified.
I set up a brand new "Cocoa Application" project in Xcode to illustrate the problem. Here is my code:
BindingTesterAppDelegate.h
#import <Cocoa/Cocoa.h>
#interface BindingTesterAppDelegate : NSObject <NSApplicationDelegate>
{
NSWindow * window;
NSArrayController * arrayController;
NSMutableArray * mutableArray;
}
#property (assign) IBOutlet NSWindow * window;
#property (retain) NSArrayController * arrayController;
#property (retain) NSMutableArray * mutableArray;
- (void)changeArray:(id)sender;
#end
BindingTesterAppDelegate.m
#import "BindingTesterAppDelegate.h"
#implementation BindingTesterAppDelegate
#synthesize window;
#synthesize arrayController;
#synthesize mutableArray;
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
NSLog(#"load");
// create the array controller and the mutable array:
[self setArrayController:[[[NSArrayController alloc] init] autorelease]];
[self setMutableArray:[NSMutableArray arrayWithCapacity:0]];
// bind the arrayController to the array
[arrayController bind:#"content" // see update
toObject:self
withKeyPath:#"mutableArray"
options:0];
// set up an observer for arrangedObjects
[arrayController addObserver:self
forKeyPath:#"arrangedObjects"
options:0
context:nil];
// add a button to trigger events
NSButton * button = [[NSButton alloc]
initWithFrame:NSMakeRect(10, 10, 100, 30)];
[[window contentView] addSubview:button];
[button setTitle:#"change array"];
[button setTarget:self];
[button setAction:#selector(changeArray:)];
[button release];
NSLog(#"run");
}
- (void)changeArray:(id)sender
{
// modify the array (being sure to post KVO notifications):
[self willChangeValueForKey:#"mutableArray"];
[mutableArray addObject:[NSString stringWithString:#"something"]];
NSLog(#"changed the array: count = %d", [mutableArray count]);
[self didChangeValueForKey:#"mutableArray"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSLog(#"%# changed!", keyPath);
}
- (void)applicationWillTerminate:(NSNotification *)notification
{
NSLog(#"stop");
[self setMutableArray:nil];
[self setArrayController:nil];
NSLog(#"done");
}
#end
And here is the output:
load
run
changed the array: count = 1
arrangedObjects changed!
changed the array: count = 2
changed the array: count = 3
changed the array: count = 4
changed the array: count = 5
stop
arrangedObjects changed!
done
As you can see, the KVO notification is only sent the first time (and once more when the application exits). Why would this be the case?
update:
Thanks to orque for pointing out that I should be binding to the contentArray of my NSArrayController, not just its content. The above posted code works, as soon as this change is made:
// bind the arrayController to the array
[arrayController bind:#"contentArray" // <-- the change was made here
toObject:self
withKeyPath:#"mutableArray"
options:0];
First, you should bind to the contentArray (not content):
[arrayController bind:#"contentArray"
toObject:self
withKeyPath:#"mutableArray"
options:0];
Then, the straightforward way is to just use the arrayController to modify the array:
- (void)changeArray:(id)sender
{
// modify the array (being sure to post KVO notifications):
[arrayController addObject:#"something"];
NSLog(#"changed the array: count = %d", [mutableArray count]);
}
(in a real scenario you'll likely just want the button action to call -addObject:)
Using -[NSMutableArray addObject] will not automatically notify the controller. I see that you tried to work around this by manually using willChange/didChange on the mutableArray. This won't work because the array itself hasn't changed. That is, if the KVO system queries mutableArray before and after the change it will still have the same address.
If you want to use -[NSMutableArray addObject], you could willChange/didChange on arrangedObjects:
- (void)changeArray:(id)sender
{
// modify the array (being sure to post KVO notifications):
[arrayController willChangeValueForKey:#"arrangedObjects"];
[mutableArray addObject:#"something"];
NSLog(#"changed the array: count = %d", [mutableArray count]);
[arrayController didChangeValueForKey:#"arrangedObjects"];
}
There may be a cheaper key that would give the same effect. If you have a choice I would recommend just working through the controller and leaving the notifications up to the underlying system.
A much better way than explicitly posting whole-value KVO notifications is to implement array accessors and use them. Then KVO posts the notifications for free.
That way, instead of this:
[self willChangeValueForKey:#"things"];
[_things addObject:[NSString stringWithString:#"something"]];
[self didChangeValueForKey:#"things"];
You would do this:
[self insertObject:[NSString stringWithString:#"something"] inThingsAtIndex:[self countOfThings]];
Not only will KVO post the change notification for you, but it will be a more specific notification, being an array-insertion change rather than a whole-array change.
I usually add an addThingsObject: method that does the above, so that I can do:
[self addThingsObject:[NSString stringWithString:#"something"]];
Note that add<Key>Object: is not currently a KVC-recognized selector format for array properties (only set properties), whereas insertObject:in<Key>AtIndex: is, so your implementation of the former (if you choose to do that) must use the latter.
Oh, I was looking for a long time for this solution ! Thanks to all !
After getting the idea & playing around , I found another very fancy way:
Suppose I have an object CubeFrames like this:
#interface CubeFrames : NSObject {
NSInteger number;
NSInteger loops;
}
My Array contains Objects of Cubeframes, they are managed via (MVC) by an objectController and displayed in a tableView.
Bindings are done the common way:
"Content Array" of the objectController is bound to my array.
Important: set "Class Name" of objectController to class CubeFrames
If I add observers like this in my Appdelegate:
-(void)awakeFromNib {
//
// register ovbserver for array changes :
// the observer will observe each item of the array when it changes:
// + adding a cubFrames object
// + deleting a cubFrames object
// + changing values of loops or number in the tableview
[dataArrayCtrl addObserver:self forKeyPath:#"arrangedObjects.loops" options:0 context:nil];
[dataArrayCtrl addObserver:self forKeyPath:#"arrangedObjects.number" options:0 context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSLog(#"%# changed!", keyPath);
}
Now, indeed, I catch all the changes : adding and deleting rows, change on loops or number :-)

Resources