I have a UITableView in my main view and an 'add' button in a UINavigationBar that will push to another view that allows the user to add another object to the tableview. I have a protocol in this view that allows the information to be sent back to the main view.
My problem is that whenever I try to add this new object sent from the protocol (which is a NSMutableDictionary) to a NSMutableDictionary property in the main view, it does not add. I have tried adding an NSLog and it says that this object is null. If I initialise this object in the viewDidLoad method, it will run whenever the UINavigationController pops the view, and resets everything in the dictionary. I do not know where to initialise the object to make sure that it keeps everything stored in it.
The protocol works fine, but I cannot do anything with the object it sends.
In AddCellViewController.h:
#import <UIKit/UIKit.h>
#protocol AddCellDelegate <NSObject>
#required
-(void)passCellInfo:(NSMutableDictionary *)cellInfo;
#end
#interface AddCellViewController : UITableViewController <UITextFieldDelegate> {
id <AddCellDelegate> delegate;
}
#property (strong) id <AddCellDelegate> delegate;
#end
in AddCellViewController.m (the method that utilises the protocol):
-(void)sendObject{
[[self delegate] passCellInfo:newCellInfo];
[self.navigationController popToRootViewControllerAnimated:YES];
}
in MainView.m:
-(void)passCellInfo:(NSMutableDictionary *)cellInfo{
[self.cellInformation setValue:cellInfo forKey:[cellInfo objectForKey:#"cell_title"]];
[self.cells addObject:[cellInfo objectForKey:#"cell_title"]];
[self.tableView reloadData];
NSLog(#"%#: cellInfo - cellInformation: %# - cells: %#",cellInfo ,self.cellInformation,self.cells); //logs the object passed from the protocol (this works), the cellInformation object, and cells object (these return null)
}
You can use a NSMutableDictionary property in a static or global class, and access it very easy from the tableview or any other view:
You feed your table from that dictionary
And you add elements from your other view into that dictionary (the protocol is not needed anymore).
Everytime your tableView appears, you should refresh the data of the table from this global class.
Some example of how to use it:
How to implement global static class
In your code you do this. You using same key every time. So it will get replaced.
[self.cells addObject:cellInfo];
I will tell you a simple way instead. Send your mutableDictionary to secondView from init method. Copy in class level mutableDictionary. Do not allocate or initialize. Add new item in that Dictionary. It will reflect in mainView dictionary. Call [tableView reloadData] in mainView viewWillAppear method.
Related
I have two table views that contain a mutable array of objects that can be instantiated by the user with various properties. The class and its properties are within separate .h and .m files that the table views access.
I would like the two table views to look identical so that when a user adds, deletes, or moves an object in one table view, the second table view is immediately updated. I understand that they will be sharing a mutable array, but I'm not sure where to put it so that changes in one table view occurs in the other table view.
Additionally, is this a situation in which I would make one of the table views a subclass of the other?
Suppose there is a controller owns the NSMutableArray object and the two table views.
Use the KVO feature, let the two table view retain (use strong as property) the NSMutableArray object.
Create a new delegates array property like NSArray< id<MyArrayNotifier> > *delegates for the NSMutableArray object's owner, implement the MyArrayNotifier protocol in two table view classes, add the table objects to delegates array. Now you could get notified when your focused message arrived.
First you should make a singleton class that contains that mutable array so you can edit/access it anywhere , whenever you update that array reload the visible tableView , if the other tableView is also visible reload it(either with delegate , observer , notificationCenter), otherwise it will be updated with the last edit when you open the VC that contains it , also you can make a use of viewDidAppear to reload the table inside it as another choice instead of delegates if it's the logic of your app
// .h
#import <Foundation/Foundation.h>
#interface GlobalData : NSObject
#property(nonatomic,retain)NSMutableArray*masterArray;
+(GlobalData*)shared;
#end
// .m
#import "GlobalData.h"
#implementation GlobalData
static GlobalData *instance = nil;
+(GlobalData *)shared
{
#synchronized(self)
{
if(instance==nil)
{
instance = [GlobalData new];
instance.masterArray = [NSMutableArray new];
}
}
return instance;
}
#end
Usage
[GlobalData.shared.masterArray addObject:"strData"];
SettingsStore.h
#interface SettingsStore : IASKAbstractSettingsStore
{
#public
NSDictionary *dict;
NSDictionary *changedDict;
}
- (void)removeAccount;
#end
menuView.m
-(IBAction)onSignOutClick:(id)sender
{
SettingsStore *foo = [[SettingsStore alloc]init];
[foo removeAccount];
[self.navigationController pushViewController:foo animated:YES];
exit(0);
}
I want to call this removeAccount function from menuView.m. But I am getting error.
How to fix it and call this removeAccount.
There are few mistakes in your Code please find them below.
[foo removeAccount]; Calling this method is correct
[self.navigationController pushViewController:foo animated:YES];
Not correct because SettingsStore is not subclass of
UIViewController only subclass of UIViewController can be pushed to
Navigation controller
exit(0); Calling this method is not
recommended by Apple
You are calling removeAccount correctly from your menuView.m file, but there are several issues with your code:
You are treating foo as though it were a UIViewController, and it's actually a member of the SettingStore class. Does the SettingStore class refer to an actual screen, or is it more a data object (for storing settings?). If it's the latter, you don't want to push it on. You can create it, and use it, but the user doesn't need to see it.
You are calling exit(0); you can remove that line. If you want to remove the menuView.m file from your memory, remove references to it (e.g. from its parent view controller).
The menuView.m file is confusing, as in, is it a view or a viewController. An IBAction I would normally stick in a ViewController file, rather than a view file. Your basic design pattern is MVC (Model / View / Controller). In this case, it seems your SettingStore file is a Model (data), the menuView.m is a View and your code is for the Controller bit.
I have a UITableView comprised of custom UITableViewCells. In each cell, there is a UILabel and a UISlider. Does anyone know how to, upon a change in value of one of the sliders, send the new value of the slider from the custom UITableViewCell (in a separate file) to the UITableViewController, so that I can then update the array from which the table was populated?
The closest I've got so far is a failed hack: firing a setSelected event when a slider value is changed. Whilst this highlights the changed custom cell, the event is not picked up by didSelectRowAtIndexPath in the UITableViewController.
Whilst code is always appreciated, a conceptual/method solution is what I am looking for.
Thank you in advance,
Jamie
What you need is called Delegate Pattern.
Quoting from there to explain what does it mean:
Delegation is a simple and powerful pattern in which one object in a
program acts on behalf of, or in coordination with, another object.
The delegating object keeps a reference to the other object—the
delegate—and at the appropriate time sends a message to it. The
message informs the delegate of an event that the delegating object is
about to handle or has just handled. The delegate may respond to the
message by updating the appearance or state of itself or other objects
in the application, and in some cases it can return a value that
affects how an impending event is handled. The main value of
delegation is that it allows you to easily customize the behavior of
several objects in one central object.
These diagrams will help you understand what goes on:
Architecture:
Operation:
Now as to how to implement it, this is what you have to do.
For Objective-C:
First of all, create delegate methods of your UITableViewCell. Lets name it ContactTableViewCell.
In your ContactTableViewCell.h file, do this:
#protocol ContactCellDelegate <NSObject>
#required
-(void) didMoveSliderWithValue:(float) value;
#end
#interface ContactTableViewCell : UITableViewCell
#property (weak, nonatomic) id<ContactCellDelegate> delegate;
Now conform your TableViewController to this delegate. Let's name your VC MyTableViewController.
In MyTableViewController.h, Do this:
#interface MyTableViewController : UIViewController <ContactCellDelegate> //Use UITableViewController if you are using that instead of UIViewController.
In your cellForRowAtIndexPath, before returning cell, add this line:
cell.delegate = self;
Add implementation for the delegate method INSIDE your MyTableViewController.m.
-(void) didMoveSliderWithValue: (float) value
{
NSLog(#"Value is : %f",value);
//Do whatever you need to do with the value after receiving it in your VC
}
Now let's get back to your ContactTableViewCell.m. In that file you must have added some IBAction to capture the value change event in slider. Let's say it is the following:
- (IBAction)sliderValueChanged:(UISlider *)sender {
self.myTextLabel.text = [#((int)sender.value) stringValue]; //Do whatever you need to do in cell.
//Now call delegate method which will send value to your view controller:
[delegate didMoveSliderWithValue:sender.value];
}
When you call delegate method, it will run the implementation that we wrote earlier in the MyTableViewController. Do whatever you need in that method.
What happens here is that your Cell sends the message to your desired VC (Which is delegate of the Cell), that "Hey, Call the delegate method that we wrote earlier in your body. I am sending you parameters right away". Your VC takes the parameters and does whatever you wanted it to do with that info and at that time.
For Swift:
First of all, your TableViewCell.swift file, create a protocol like this:
#class_protocol protocol ContactCellDelegate {
func didMoveSliderWithValue(value: Float)
}
Now in your Cell class, create a delegate property like:
var cellDelegate: ContactCellDelegate?
In your Slider IBAction, call the delegate method like this:
self.cellDelegate?.didMoveSliderWithValue(slider.value)
In your VC do these changes:
Make it conform to the delegate:
class MyTableViewController: UIViewController, ContactCellDelegate
Add this line before returning cell in cellForRowAtIndexPath
cell.cellDelegate = self //Dont forget to make it conform to the delegate method
Add the implementation of required delegate method:
func didMoveSliderWithValue(value:float) {
//do what you want
}
I have kept the Swift part precise and summarized because It should be very easy to change the detailed Obj-C explanation to Swift implementation. However If you are confused about any of the pointers above, leave a comment.
Also see: StackOverflow answer on using Delegate pattern to pass data back
I have an application where A View Controller (A)is called twice in close succession. Now each time it is called, an NSString object is created, and I need this value to be stored in an NSMutableArray that is a public property of ANOTHER View Controller (B).
In A, I create an instance of the second View Controller (B), and using that instance, add the NSString objects into the NSMutableArray which I've created as a public property. Later, when I am inside View Controller B and print the contents of the NSMutableArray property, the array is empty. Why? Here is the code that is inside View Controller A:
-(void)viewDidLoad {
ViewControllerA *aVC = [[ViewControllerA alloc] init];
if (aVC.stringArray == nil) {
aVC.stringArray = [[NSMutableArray alloc] init];
}
[aVC.stringArray addObject:#"hello"];
[aVC.stringArray addObject:#"world"];
for (NSString *wow in aVC.stringArray) {
NSLog(#"The output is: %#", wow);
}
}
Inside my View Controller B class, I have the following code:
- (IBAction)buttonAction:(UIButton *)sender {
NSLog(#"Button selected");
for (NSString *test in self.stringArray) {
NSLog(#"Here are the contents of the array %#", test);
}
}
Now the buttonAction method gets called, as I do see the line Button selected in the system output, but nothing else is printed. Why? One thing I want to ensure is that View Controller A is called twice, which means I would like to see in the output, "Hello World", "Hello World" (i.e. printed twice), and not "Hello World" printed just once.
The other thing I wish to point out is that View Controller B may not be called at all, or it may be called at a later point in time. In any case, whenever View Controller B is called, I would like to have the values inside the array available, and waiting for the user to access. How do I do this?
Your approach is not ideal, potentially leading to a memory cycle, with two objects holding strong pointers to each other.
You can instead achieve your goal in two ways;
Delegate Protocol
This method allows you to set delegates and delegate methods to pass data back and forth between view controllers
in viewControllerA.h
#protocol viewControllerADelegate <NSObject>
- (void)addStringToNSMutableArray:(NSString *)text;
#end
#interface viewControllerA : UIViewController
#property (nonatomic, weak) id <viewControllerADelegate> delegate;
in viewControllerB.m
// create viewControllerA class object
[self.viewControllerA.delegate = self];
- (void)addStringToNSMutableArray:(NSString *)text
{
[self.mutableArray addObject:text];
}
in viewControllerA.m
[self.delegate addStringToNSMutableArray:#"some text"];
Utility Classes
Alternatively you can use a utility class with publicly accessible methods (and temporary data storage). This allows both viewController classes to access a shared data store, also if you use class methods, you don't even need to instantiate the utility class.
in XYZUtilities.h
#import <Foundation/Foundation.h>
#interface XYZUtilities : NSObject
+ (void)addStringToNSMutableArray;
#property (strong, nonatomic) NSMutableArray *array;
#end
in XYZUtilities.m
+ (void)addStringToNSMutableArray
{
NSString *result = #"some text";
[self.array addObject:result];
}
+ (NSArray)getArrayContents
{
return self.array;
}
in viewControllerA.m
NSString *stringFromObject = [XYZUtilities addStringToNSMutableArray];
in viewControllerB.m
self.mutableArray = [[NSMutableArray alloc] initWithArray:[XYZUtilities getArrayContents]];
I'm not sure what kind of a design pattern you are trying to follow but from the looks of it IMHO that's not a very safe one. However, there are many, many ways this could be accomplished.
One thing though, you said that View Controller B may never get allocated and if it is alloc-ed, it will be down the road. So you can't set a value/property on an object that's never been created.
Since you already aren't really following traditional patterns, you could make a static NSMutableArray variable that is declared in the .m of your View Controller B Class and then expose it via class methods.
So it would look like this:
viewControllerB.h
+(void)addStringToPublicArray:(NSString *)string;
viewContrllerB.m
static NSMutableArray *publicStrings = nil;
+(void)addStringToPublicArray:(NSString *)string{
if (publicStrings == nil){
publicStrings = [[NSMutableArray alloc]init];
}
if (string != nil){
[publicStrings addObject:string];
}
}
Then it would be truly public. All instances of view controller B will have access to it. This, of course is not a traditional or recommended way of doing it—I'm sure that you will have many replies pointing that out ;).
Another idea would be to use a singleton class and store the values in there. Then, when or if view controller B is ever created, you can access them from there.
I have a property on a ViewController which I set from a parent SplitViewController:
Property declaration/synthesization
#interface DownloadRecipesViewController_iPad : DownloadRecipesViewController<PopoverMenuDelegate, RecipeChangeDelegate>{
id <NSObject, PopoverMenuParentDelegate, RecipeChangeDelegate, RecipeDownloadedDelegate> _selectionDelegate;
}
#property (strong) UIBarButtonItem *btnMenu;
#property (strong) id <NSObject, RecipeChangeDelegate, RecipeDownloadedDelegate> selectionDelegate;
#implementation DownloadRecipesViewController_iPad
#synthesize btnMenu;
#synthesize selectionDelegate = _selectionDelegate;
I wire up the delegate in the parent SplitViewVC's viewDidLoad method:
Wiring up the delegate
self.downloadVc = [self.storyboard instantiateViewControllerWithIdentifier:#"RecipeDownload"];
[self.downloadVc setSelectionDelegate:self];
A button in the Child VC calls a method to fire an event up to the parent ViewController, but when this event is called, the delegate is nil and the event isn't fired. I've wracked my brains trying every which way to find out why this happens but I'm at a total loss.
Delegate is nil here (firing the delegate):
-(IBAction)didTapMenu:(id)sender{
if([_selectionDelegate respondsToSelector:#selector(shouldToggleMenu)])
[_selectionDelegate shouldToggleMenu];
}
I've also tried without the backing property but hit the same problem.
Suggestions on how to find this follow. But first, why not save yourself some typing and remove the ivar and remove the #synthesize - its totally unnecessary typing at this time. Also, as a comment said, delegates should almost always be typed as weak.
Suggestions:
1) Write a (temporary) setter for the selectionDelegate, then set a break point where you actually set the value (or after) so you can verify that its getting set, and that nothing else is zeroing it out.
2) Set a breakpoint on the IBAction method, on the line where the if statement is, and when you hit it verify the object is the same one where you set the delegate, what the delegate value is, and then see if the respondsTo method succeeds (use single step).
The way I eventually solved this was:
Create a new delegate which exposes a method: -(void)segueBeginning:(UIStoryboardSegue*)destination
Use this delegate to expose prepareForSegue from the child UINavigationController to the parent SplitViewController
.3. Hook up my child VC in the parent ViewController when prepareForSegue is raised in the child nav controller:
if([destination.destinationViewController isKindOfClass:[DownloadRecipesViewController_iPad class]] && !self.downloadVc){ // download recipes
self.downloadVc = destination.destinationViewController;
self.downloadVc.selectionDelegate = self;
[self.downloadVc setSnapshotChangeDelegate:self];
[self.downloadVc.navigationItem setRightBarButtonItems:[NSArray arrayWithObjects:btnAdd, nil]];
}