UICollectionView inside of a Container View - Refresh on button click - ios7

Problem
As illustrated, I have a container view (B) which resides inside of a view with other controls on it (A). The container view (B) holds a collection view, which I would like to update whenever a button is pressed on view (A).
I have gone through the UICollectionView Basics but feel I must be missing something. My natural response when wanting to communicate between UIViewControllers is to go off and build something based on either a callback or other delegate mechanism. Before I reinvent the wheel, any thoughts?
Currently when I click the buttons in the view, my collection data is updated and I call setNeedsDisplay and reloadData on the collection view (accessed via the childViewControllers property). I've tried calling setNeedsDisplay on the container view itself as well (no joy).
BTW - I have reviewed similar SO questions, which do not provide a matching use-case but do seem to indicate a lack of insight on this particular type of issue (if I've missed a great answer please let me know):
A, B, C, D
Solution
Please note that I've shared my solution below but additional answers are still welcome (especially if it's a better way)

Solution
Since my search of the community showed marginal information on this topic, I am sharing my solution. I added a callback function on my data controller. Then upon closer inspection of How to tell UICollectionView about your content, I realized that I could simplly toggle the data source as indicated below (so I did a combination of both):
Please note the multiple options mentioned on how to get a UICollectionView to update itself.
Details
In my .h file for my data controller
//above #interface
typedef void (^CallbackBlock)();
//within #interface
#property (strong, nonatomic) CallbackBlock onDataUpdated;
//within my data controller method where data was changed
[DataManager sharedManager].onDataUpdated(); //singleton, i know...
//alternative to above singleton
__weak DataManager* weakSelf = [[DataManager alloc]init];
weakSelf.onDataUpdated();
//In my UIViewController (B) subclass
#property (strong,nonatomic)DataManager* controller;
//In my view did load of UIVC (B)
self.controller = [DataManager sharedManager];
__weak ProgressViewController* weakSelf = self;
self.controller.onDataUpdated = ^(){
//perform my action
[weakSelf setData:nil]; //remove existing data
[weakSelf setData:[self getSomeData]]; //get some new data
[[weakSelf progressCollectionView]reloadData];
[[weakSelf progressCollectionView] setNeedsDisplay];
};
End result:
UIVC (A) receives button click
Data Manager class fires the data updated event at end of method execution for step 1
UIVC (B) instantly reloads the data without refreshing other subviews of UIVC (A)

Related

Correct method to present a different NSViewController in NSWindow

I am developing an app that is a single NSWindow and clicking a button inside the window will present a NSViewController, and a button exists in that controller that will present a different NSViewController. I know how to swap out views in the window, but I ran into an issue trying to do this with the multiple view controllers. I have resolved the issue, but I don't believe I am accomplishing this behavior in an appropriate way.
I originally defined a method in the AppDelegate:
- (void)displayViewcontroller:(NSViewController *)viewController {
BOOL ended = [self.window makeFirstResponder:self.window];
if (!ended) {
NSBeep();
return;
}
[self.box setContentView:viewController.view];
}
I set up a target/action for an NSButton to the AppDelegate, and here's where I call that method to show a new view controller:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self displayViewcontroller:newVC];
}
This does work - it presents the new view controller's view. However if I then click any button in that view that has a target/action set up that resides within its view controller class, the app instantly crashes.
To resolve this issue, I have to change didTapContinue: to the following:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self.viewControllers addObject:newVC];
[self displayViewcontroller:[self.viewControllers lastObject]];
}
First of all, can you explain why that resolves the issue? Seems to be related to the way the controller is "held onto" in memory but I'm not positive.
My question is, how do I set this up so that I can swap out views from within any view controller? I was planning on getting a reference to the AppDelegate and calling displayViewcontroller: with a new controller I just instantiated in that class, but this causes the crash. I need to first store it in the array then send that reference into the method. Is that a valid approach - make the viewControllers array public then call that method with the lastObject, or how should this be set up?
What is interesting in your code is that you alloc/init a new view controller every time that you call the IBAction. It can be that your view its totally new every time you call the IBAction method, but I would think that you only have a limited number of views you want to show. As far as my knowledge goes this makes your view only to live as long as your IBAction method is long. That the view still exists, is because you haven't refreshed it. However, calling a method inside a view controller that is not in the heap anymore (since you left the IBAction method and all local objects, such as your view controller are taken of the heap thans to ARC) makes the app crash, because you reference a memory space that is not in use or used by something else.
Why does the app work when you ad the view to the viewcontrollers array? I assume this array is an array that has been initiated in the AppDelegate and now you add the view controller with a strong reference count to the viewcontrollers array. When you leave the IBAction method, the view controller still has a strong reference and ARC will not deallocate the view controller.
Is this the proper way? Well, it works. I would not think it is considered very good programming, since you don't alloc/init an object in a method that needs to stay alive after leaving the method. It would be better practice to allocate and initialize your view controller(s) somewhere in an init, awakeFromNIB or a windowDidLoad method of your AppDelegate. The problem with your current solution is that you are creating an endless array of view controllers of which you only use the last. Somewhere your program will feel the burden of this enormously long array of pretty heavy objects (view controllers) and will run out of memory.
Hope this helps.
By the way, this is independent of whether you use Mavericks or Yosemite. I was thinking in a storyboard solution, but that wouldn't answer your question.
Kind regards,
MacUserT

Return for a push segue

I am new to story boards and I am trying to move between table view controllers.
I understand how to set up a segue and how to send data to the new story board but my question is returning data back. If I use the push style segue it gives me an automatic back button. I want a page that will create a "job" and save it if they hit save (a bar button I created on the other side of the title) When I set up the segue to go back to the main page from the save button it made that main table view controller a child (instead of simply going back to it's original state). The work around I was thinking was saving it to a file when they hit save and whenever they load the main table view it loads from that file. Is this a common and correct way to do this or should I be trying to return that object and save it in the main table view controller?
A common approach is to use delegation. Since ViewControllers should be as independent as it is possible, we have to minimize dependencies between them. And actually your idea with the file, if I understand it correctly, does it as well. But using files to organize a communication between ViewControllers is not very convinient.
Usually you declare a #protocol SecondViewControllerDelegate for your second ViewController (where you click a "Save" button). With methods like:
#protocol YourSecondViewControllerDelegate <NSObject>
-(void)yourSecondViewControllerDidCancel:(YourSecondViewController*)controller; //if you have a cancel button
-(void)yourSecondViewControllerDidFinish:(YourSecondViewController*)controller yourDataToReturn:(SomeData*)data andSomeMoreData:(AnotherDataType*)data2;
#end
Then you add a property like this to your SecondViewController:
#property (nonatomic, assign) id<YourSecondViewControllerDelegate> delegate; //Do not retain it!
Then you adopt your protocol in your MainViewController.
#interface MainViewController()<YourSecondViewControllerDelegate> //you can do it in the private category to keep the class interface clear.
#end
#implementation
//don't forget to implement those methods here :)
#end
And when you trigger your segue from the MainViewController, you can set your MainViewController as a delegate for the SecondViewController:
SecondViewController *destinationController = [[SecondViewController alloc] init]; //Just for example.
destinationController.delegate = self;
//trigger a segue :)
When the user presses the Save button in the SecondViewController, you call the delegate's yourSecondViewControllerDidFinish:
[self.delegate yourSecondViewControllerDidFinish: self yourDataToReturn: someData andSomeMoreDate: someMoreData];
This way MainController's yourSecondViewControllerDidFinish method will be called. And you can pick someData and someMoreData up there.
You can get more details about it in this tutorial:
Apple's your second iOS app tutorial
If you're making a segue to go "back" to a controller, then that segue needs to be an unwind segue. An unwind segue goes back to the same instance of the controller that you came from originally. If you need to send data back to that controller, you can do it in prepareForSegue.

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.

Push view to navigation controller from button in other class

I have a UIButton that I create in my sub class ViewController, and add it to my MainViewController.
Now, I added a target method to this button that should push another view controller to my Navigation controller (the one that in the MainViewController).
I know that the method did call when I push the button, but the view wasn't push to the Navigation Controller.
I scanned this drawing - this is the drawing (I also added part of my code):
This is the code I'm using in my button:
(remember it's in a deferent ViewController).
- (void)buttonPressed:(UIButton *)sender
{
Photo_ScreenGlobalView *photo = [[Photo_ScreenGlobalView alloc] init];
[self.navigationController pushViewController:photo animated:YES];
}
Usually I solve these situations with delegation. If there is a view controller which is subordinate to another (i.e. a "sub" view controller) but should have the ability to trigger navigation changes, etc... then it should have a weak pointer back to it's 'parent'. Then the parent VC should implement an appropriately named protocol with a callback for the child to use. The names of these things can be generic, such as #property navigationDelegate and requestNavigationToViewController: or they can be more semantic, such as #property userFormDelegate and userFormDoneButtonPressed:
Generally speaking, a subordinate view controller should not be able to directly modify navigation at it's parent's level; but it can trigger it via more loosely-coupoled interfaces like these.
i came back to let you all know how i actually did it.
after googling a lot found this nice and quick guide how to make DELEGATE
and working with delegate solved all my problems. if you need any help don't hesitate to send me PM.
this is the guide:
http://css.dzone.com/articles/do-not-publishcreating-your

Loading a Custom ViewController with data

I have a UITableView which loads through it's navigationController a new viewcontroller.
This code goes in the tableView:didSelectRowAtIndexPath method:
ConcertDetailViewController *detailVC = [[ConcertDetailViewController alloc] initWithNibName:#"ConcertDetailViewController" bundle:nil];
The UITableView has a model, I want to sent an element of this model to the newly created ViewController.
detailVC.aProd = [_prod objectAtIndex:indexPath.row];
When the value is set I want the detailVC to draw the data on the screen. I thought a custom setter, overwriting the one generated by #synthesize would work.
-(void)setaProd:(NSMutableDictionary *)aProd {
_aProd = aProd;
[self displayAProd];
}
displayAProd just takes the values in aProd and put's them on the screen, or rather I'm setting some value of an outlet , created in my nib file.
self.prodNameLbl.text = [_aProd objectForKey:#"name"];
Nothing special about this. But it just doesn't work. I figured out why, I think.
It's because the setter executes way faster then, loading the whole view into memory.
If I put self.prodNameLbl.text = #"something"; in the viewDidLoad method it does display the correct value in the label.
A quick workaround would be the see if _concerts has been set and from there call displayAProd. Here I'm doubting myself if it's a good way to load a view. What if the custom setter takes longer to execute the loading the view. The test to see if _concerts has been set will be false and nothing will be displayed. Or is that just impossible to happen ?
Or maybe there's a better pattern for loading views and passing data to them to be displayed.
Thanks in advanced, Jonas.
The problem is that when you load the view controller from the NIB, the IBOutlets will not be connected to your UILabel and other similar properties during the initWithNibName call.
You need to wait for viewDidLoad to be called on detailVC and call [self displayAProd] from there. At this point, the connections will have been made.
Do a quick test. Put a break point in your didSelectRowAtIndexPath method and, after initialising detailVC, check to see if prodNameLbl is null or not.