Problem with NSCollectionView - objective-c

I'm quite a rookie when it comes to Cocoa programming, so I hope some experts can hint me on the right direction with a problem I'm facing.
I have a NSCollectionView that is bound to a NSArrayController (A) of elements having two properties: an URL to a picture and a NSArray (B) of elements of another class.
For every element inside the NSArrayController (A) , I load a subview with a NSImageView that should display the image in the URL and a NSTableView that is bound to the elements of the NSArray (B).
Everything works fine, except for one very important thing: the URL to the picture is not immediately available when I create the subview, it becomes available later, but when it does, I don't see the picture loading in the NSImageView.
I would think of a problem in the KVC/KVO implementation, but the strange thing is that the image loads correctly if, when the URL becomes available, the subview is not visible (e.g in a portion of the scrollview that is not displayed).
Example: The NSScrollview size is such that it can display only two subviews at a time. I add three elements to the NSArrayController (A): the first two images don't load, if I scroll down the scrollview to see the third element, I find the image loaded correctly.
Any ideas about what could cause such a strange behaviour?
Thank you in advance
Luca
series.h
#interface Series : NSObject {
#private
NSMutableString * banner;
}
-(Series*)initWithEpisode:(Episode*)ep;
-(void)setBanner:(NSString*)_banner;
#property (retain, readwrite) NSMutableString *banner;
#end
series.m
#implementation Series
#synthesize banner;
-(Series*)initWithEpisode:(Episode*)ep
{
self = [super init];
if(self){
banner = [[NSMutableString alloc]initWithString:#"some invalid URL"];
}
-(void) setBanner:(NSString*)_banner
{
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
}
-(void)dealloc
{
[super dealloc];
[banner release];
}
SeriesListViewController.m
-(void)VideoChangedStatus:(Episode*)ep{
//This is the delegate called by the object Episode when it retrieves the URL via asynchronous call to a webservice
Series *tmp = [[Series alloc]initWithEpisode:ep];
[[seriesList objectAtIndex:[seriesList indexOfObject:tmp]]setBanner:[ep banner]];
}
The binding is done in the subview nib file, to the NSImageView: I set File's Owner of type NSCollectionViewItem and then bind Valueurl to representedObject.banner
I didn't subclass NSCollectionView nor NSCollectionViewItem

After days of trying I found a solution that works: apparently it's not enough to use the setString method, I need to re-intialize the property inside the setBanner method
-(void) setBanner:(NSString*)_banner
{
banner = [NSMutableString[NSString stringWithFormat:#"some root path/%#", _banner]];
}
Still, I'd be very glad to know if someone has an explanation of why setString was causing that strange (to me) problem and why this solution works.
Thank you
Luca

I’m not sure why you’ve declared banner to be a mutable string — it looks like an immutable string would suffice.
At any rate, when you write a custom setter method you need to send -willChangeValueForKey: and -didChangeValueForKey: to ensure KVO (and hence bindings) compliance:
-(void) setBanner:(NSString*)_banner
{
[self willChangeValueForKey:#"banner"];
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
[self didChangeValueForKey:#"banner"];
}

Related

How to communicate between two NSView objects?

With Cocoa, I hope to display one image in one image view. Whenever I click on the showed image. A piece of predefined-size of the image around the click point will be shown in another image view in the same window with the first one. So details will be seen.
To do this, I write my own MyImageView inherited from NSImageView. In this class, I implement the mouseUp method. In this method, I do all the image coordinate things. I can do this right so far. And the new piece will be stored as a property.
#import "MyImageView.h"
#implementation MyImageView
#synthesize point;
#synthesize small_img;
#synthesize big_img;
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.point = NSMakePoint(0.0, 0.0);
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
}
- (void)mouseUp:(NSEvent *)theEvent
{
//calculate the small image and stored in small_img
}
#end
Then I initialize an instance in the AppDelegate and do something like
[small_view setImage: myImageView.small_img]
But no small image will show.
It seems a delegate mechanism will work out. However, I am very confused with the View-Controller-Delegate pattern. And I can't find any material explaining the communication between two NSView subclass objects with sample code I can understand. I am totally a novice in this domain.
Hope somebody help me get this done since it's very important to me.Thanks!
You're right about the delegate mechanism could work. But there are other options.
Basically you have two ImageViews (or subclasses) and they can't communicate. The only piece they have in common is the container or owner (usually a ViewController, but it could be the AppDelegate), so let's call this object just the owner.
To communicate, the clicked ImageView would need to notify the owner that something happened, and the owner in turn would forward this notification to the second ImageView. The first notification can be done through a delegate, and the second method is straightforward (calling [mySecondImageView someMethod];)
It would be too long to explain here how to use a delegate, and there are many examples out there. In short, the first ImageView has a delegate property and the owner sets itself as the delegate (something like myFirstImageView.delegate = self];
If this is too complicated, another solution which might fit well here is using Notifications. Again, there are many examples, but in short, a notification would allow communicating your two ImageViews without intervention of the owner. This type of communication is good for loosely coupled objects.
EDIT:
You can of course set the second view as the delegate and it would work perfectly. I personally like to centralize control over all my objects in my ViewController, but this is my personal preference.
What kind of delegate you need? You would need to create a custom delegate (a #protocol).
#protocol imageViewProtocol
- (void)imageClicked:(... arguments...);
#end
Your FirstImageView would have a property declared in the #interface.
#interface FirstImageView ...
#property (...) id<imageViewProtocol> delegate;
...
#end
#implementation FirstImageView ...
- (void)ImageClick:(id)sender
{
// do stuff
[self.delegate imageClicked:(...arguments...)];
}
#end

Changes not reflected across view when using binding in cocoa

I am creating some sample applications to understand the concepts of view navigation, binding etc in cocoa.
Here is the scenario:
I have a window that has a tab view(2 tabs) in MainMenu.Xib.
I have a text field in the first tab and label in the second tab. I want both of them to reflect the same value and I want to do this using binding. Also, I don't want to use the views provided to me along with the tab view.
These are the steps I have done.
The view of each tab view item is set separately in the applicationDidFinishLaunching: method using the following code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
//initialize view controllers
view1=[[ViewTab1 alloc] initWithNibName:#"ViewTab1" bundle:nil];
view2=[[ViewTab2 alloc] initWithNibName:#"ViewTab2" bundle:nil];
//set views
[[[myTabView tabViewItems] objectAtIndex:0]setView:view1.view];
[[[myTabView tabViewItems] objectAtIndex:1]setView:view2.view];
}
myTabView is the outlet reference of the tab view from MainMenu.xib in AppDelegate.
ViewTab1 is the name of the first view controller (and the xib).
ViewTab2 is the name of the second view controller (and the xib).
ViewTab1 has one single text field (and an associated label). I have bound this to a variable(name) declared in AppDelegate.
ViewTab2 has a label. I have bound this also to the same variable in AppDelegate.
The variable, 'name' is initialized in the init method of AppDelegate.
AppDelegate.h
....
NSString *name;
....
#property(strong) ViewTab1 *view1;
#property(strong) ViewTab2 *view2;
#property (assign) IBOutlet NSTabView *myTabView;
#property (strong) NSString *name;
....
AppDelegate.m
....
#synthesize myTabView;
#synthesize view1,view2;
#synthesize name;
....
- (id)init {
self = [super init];
if (self) {
name=#"dummy";
}
return self;
....
Apart from this I haven't done any coding in my program.
In the ViewTab1.xib I got an object and made it an instance of AppDelegate and then connected the delegate reference of the Application object(NSApplication) to the same object. (I hope this is the right way of getting the AppDelegate object.)
I did the same in ViewTab2.xib
Then I bound the text field in ViewTab1 and label in ViewTab2 to this variable in AppDelegate.
When I run the program both the text field and label shows "dummy". But when I change the value in the text field, its not reflected in the label in the second tab( i.e. ViewTab2).
Please tell me what I'm doing wrong.
How to establish binding to the same App delegate object from any loaded Nib?
Yes, I know this frustrated situation as described in question... after many weeks and hundreds pages of documentation for KVO - Notifications - Bindings I think there is one very simple solution for that.
As we can find in some information sources the nib-loading process produce new instances of members... and we need to use binding connection to the old one.
Note that bindings made in InterfaceBuilder are redirect to these new instances automatically after loading nib
Why not redirect the pointer of App delegate to the old instance?
In method where you loads your nib you can test which object is app delegate before and just after nib load.
If the new one isn’t the same as the previous one you can redirect it as you want.
This simple example works for me in Xcode3 under 10.5.8 with target to OSX10.5 / i386:
// ***** SOMEWHERE IN DEFAULT APP-DELEGATE.m IMPLEMENTATION
- (IBAction) createOtherWindowFromNib: (id)sender
{
// ensure that app delegate is set as you want...
[NSApp setDelegate:self];
NSLog(#"APP-DELEGAT **** CREATE-TEST-WINDOW ***** WHO IS APP-DELEGATE BEFORE NIB LOAD: %# ", [[NSApp delegate] description]);
// we can bind members of the nib to this controller over proxy object named "File’s Owner"
NSWindowController *otherWinCapo = [[NSWindowController alloc] initWithWindowNibName: #"OtherTestWindow"];
NSLog(#"APP-DELEGAT **** CREATE-TEST-WINDOW ***** WHO IS APP-DELEGATE AFTER NIB LOAD: %# ", [[NSApp delegate] description]);
// make some test for delegates before/after here if you need ...
// usually your bindings made inside "OtherTestWindow.xib" by IB doesn’t works in this moment
// ... and some redirection if needed
[NSApp setDelegate:self];
// afer that the bind made in IB inside "OtherTestWindow.xib"
// referred to (proxy object) "Application.delegate.myBOOL" (Bind to:Application, Model Key Path:delegate.myBOOL)
// react to changes of myBOOL placed in default app delegate object as expected
// simultaneously in every open instance of "OtherTestWindow.xib"
[otherWinCapo showWindow: otherWinCapo.window]; // we need populate the window instance on screen to see it
}
I think the problem is that the objects in your xibs that you set to the app delegate class create 2 different instances of the app delegate, so changing the value of the text field changes the value of name in one instance but not in the other. That's what you're doing wrong, unfortunately, I can't think of a solution at this time.
Have you turned on 'Continuously Updates Value' in the NSTextField controls?
See this example.

Set value of property in main view controller from dynamically instantiated class (objective-c)

I am totally out of ideas on this... I've tried so many variations that I am dizzy...
I have a main UIViewController which, at the touch of a button, adds another UIViewController to one of its subviews. When the dynamic UIVC gets added, a property in the main UIVC is updated to hold a reference to it (called currentObject). This is working fine.
The problem I am having is that if I add another dynamic UIVC, the property holding the reference is initially updated correctly, but no matter what I try, I can't get the property to update when I touch the first dynamic UIVC. Everything I try to set "currentObject" from a dynamic UIVC gives me an "unrecognized selector sent to class" error and then bails.
I'm holding off from putting code into this post at first. Not sure what I would post that would be helpful.
Thanks in advance!
Updated:
in DynamicModuleViewController.h:
#interface DynamicModuleViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPopoverControllerDelegate, UIGestureRecognizerDelegate, UITextViewDelegate, UIApplicationDelegate, MFMailComposeViewControllerDelegate>{
DynamicModule *currentObject;
}
#property(nonatomic, retain) DynamicModule *currentObject;
in DynamicModuleViewController.m:
#implementation DynamicModuleViewController
#synthesize currentObject;
-(void)addObject:(id)sender
{
DynamicModule *dm = [[DynamicModule alloc]init];
// positioning and PanGesture recognition code to allow dragging of module
currentObject = dm;
[mainView addSubview:currentObject.view];
}
#end
when added this way, from a button tap, it works fine.
Once more DynamicModules are instantiated, I need to update currentObject with the DynamicModule that was tapped last.
adds another UIViewController to one of its subviews
First of all this is a red flag to me. Adding a UIViewController's view as a subview is almost always the wrong way to manage a view hierarchy and a common mistake in iOS apps. Please see http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/ so I can stay on topic and avoid repeating myself.
currentObject = dm;
This sets the ivar backing your currentObject property directly. You are leaking the previous value of currentObject. You don't appear to be removing the previous currentObject's view from mainView. I suspect you are setting the currentObject to an autoreleased object, failing to retain it because you bypassed your setter, and eventually try to send a message to the released object resulting in either an "unrecognized selector" error as your message reaches whatever other object now occupies that memory address or a BAD_ACCESS error as you try to reference an object which no longer exists.
self.currentObject = foo is equivalent to [self setCurrentObject:foo] and is probably what you intended.
What about using the delegate pattern?

Using the same NIB with multiple View Controllers

Basically I want to use a nib file and view controller as a template for a view that I plan to create a number of times. This nib will have a couple of labels and custom views.
The idea is that I will iterate through an array of objects and for each I will create an instance of this controller and set a property to that of the object from the array.
This all works nicely at the moment except for one thing - the labels won't update when I call setStringValue: !!!
I'm using a method within the view controller's code to make the change but it just doesn't work, I'm guessing that the IBOutlet isn't being hooked up properly, which is strange because the custom views are hooking up perfectly.
Any ideas?
Set a breakpoint on awakeFromNib and look in the debugger what the value of the label outlet is. All outlets should have been connected before awakeFromNib is being called. If it is still nil, you have your answer. Calling setStringValue: on nil does exactly "nothing". In that case you have not correctly bound the outlet or maybe you once had it bound correctly and later on changed the name, in that case there should be a yellow warning triangle in Xcode4 or interface builder indicating that something is wrong; however it will not prevent your app from building or running, the outlet will simply keep its initial value after object creation (which is nil).
When you alloc your NSViewControllers, just init with name of NIB:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
Thanks for the replies, they were helpful but not quite what I was getting at.
I ended up solving it by creating an empty NIB and filling it with just a custom NSView and a few other controls. I created an NSView subclass with IBOutlets for those controls and set the custom view's identity to my subclass in interface builder.
The trick in getting it to work each time I wanted to draw it was by making a class method in my subclass that would load the nib and return the view set up the way I wanted.
Code below:
+(id)todoViewFromNibWithFrame:(NSRect)frameRect todoList:(TodoList *)aTodoList
{
NSNib *todoViewNib = [[NSNib alloc] initWithNibNamed:#"TodoView" bundle:nil];
NSArray *objects = nil;
id todoView = nil;
[todoViewNib instantiateNibWithOwner:nil topLevelObjects:&objects];
for (id object in objects) {
if ([object isKindOfClass:[self class]]) {
todoView = object;
[todoView setTodoList:aTodoList];
break;
}
}
[todoViewNib release];
return todoView;
}
Thanks again for the replies!
Steve

UIViewController parentViewController access properties

I know this question has been asked several times and I did read existing posts on this topic but I still need help.
I have 2 UIViewControllers - parent and child. I display the child UIViewController using the presentModalViewController as below:
ChildController *child =
[[ChildController alloc] initWithNibName:#"ChildView" bundle:nil];
[self presentModalViewController:child animated:YES];
[child release];
The child view has a UIPickerView. When user selects an item from UIPickerView and clicks done, I have to dismiss the modal view and display the selected item on a UITextField in the parent view.
In child's button click delegate, I do the following:
ParentController *parent =
(ParentController *)[self.navigationController parentViewController];
[parent.myTextField setText:selectedText];
[self dismissModalViewControllerAnimated:YES];
Everything works without errors. But I don't know how to load the parent view so that it displays the updated UITextField.
I tried
[parent reloadInputViews];
doesn' work. Please help.
Delegation is the way to go. I know some people that may be looking for an easier solution but trust me I have tried others and nothing works better than delegation. So anyone having the same problem, go read up on delegation and follow it step by step.
In your subviewcontroller.h - declare a protocol and declare delegate mthods in it.
#protocol myDelegate
-(void)clickedButton:(subviewcontroller *)subController;
#end
In your subviewcontroller.h, within #interface:
id<myDelegate> delegate;
#property (nonatomic, assign) id<myDelegate> delegate;
NSString *data;
-(NSString *)getData;
In your subviewcontroller.m, synthesize myDelegate. Add the following code to where you want to notify your parentviewcontroller that the subview is done doing whatever it is supposed to do:
[delegate clickedButton:self];
and then handle getData to return whatever data you want to send to your parentviewcontroller
In your parentviewcontroller.h, import subviewcontroller.h and use it's delegate
#import "subviewcontroller.h"
#interface parentviewcontroller : VUIViewController <myDelegate>
{}
In your parentviewcontroller.m, implement the delegate method
- (void)clickedButton:(subviewcontroller *)subcontroller
{
NSString *myData = [subcontroller getData];
[self dimissModalViewControllerAnimated:YES];
[self reloadInputViews];
}
Don't forget memory management!
If a low-memory warning comes in during your modal view's display, the parent's view will be unloaded. Then parent.myTextField is no longer referring to the right text field until the view is reloaded. You can force a reload of the view just by calling parent.view;
However, a better idea might be to have the parent view have a String property that can be set by the child view. Then, when the parent view reappears, put that data into the text field, inside viewWillAppear: for example. You'd want to have the value set to some default value for when the parent view initially shows up too.
-(void) viewWillAppear:(BOOL) animated doesn't get called for me either, exactly when it's a modal view controller. No idea why. Not incorrectly overridden anywhere in this app, and the same problem occurs on the other 2 apps I'm working on. I really don't think it works.
I've used the delegate approach before, but I think that following approach is pretty good as well.
I work around this by adding a private category to UIViewController, like so:
.h file:
#interface UIViewController(Extras)
// returns true if this view was presented via presentModalViewController:animated:, false otherwise.
#property(readonly) BOOL isModal;
// Just like the regular dismissModalViewController, but actually calls viewWillAppear: on the parent, which hasn't been working for me, ever, for modal dialogs.
- (void)dismissModal: (BOOL) animated;
#end
and .m file:
#implementation UIView(Extras)
-(BOOL) isModal
{
return self == self.parentViewController.modalViewController;
}
- (void)dismissModal: (BOOL) animated
{
[self.parentViewController viewWillAppear: animated];
[self dismissModalViewControllerAnimated: animated];
}
#end
which I can now call like this when I want to dismiss the dialog box:
// If presented as a modal view, dismiss yourself.
if(self.isModal)
[self dismissModal: YES];
and now viewWillAppear is correctly called.
And yes, I'm donating a bonus 'isModal' property, so that the modal view can tell how it was being presented, and dismiss itself appropriately.