How to Push Data From one WKInterfaceController to Another? - ios

I have 2 Interface Controllers in my WatchKit. The first one is called InterfaceController while the other is called DetailsForWatch. IC has a tableView on it. It parses data from a Parse class, and displays data from each entry in the class as a row. This works fine.
What I am trying to do is pass the PFObject for the selected row to a PFObject in DetailsForWatch. My setup for DFW is:
.h
#interface DetailsForWatch : WKInterfaceController {
}
#property (nonatomic, retain) IBOutlet WKInterfaceLabel *detailsLabel;
#property (nonatomic, retain) PFObject *finalObject;
#end
.m
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
NSString *details = self.finalObject [#"Request"];
[self.detailsLabel setText:details];
NSLog(#"%#", self.finalObject);
// Configure interface objects here.
}
In IC, for .h I have:
#class DetailsForWatch;
#interface InterfaceController : WKInterfaceController {
DetailsForWatch *_theDetails;
}
#property (retain) DetailsForWatch *theDetails;
#end
In the .m I have:
#synthesize theDetails = _theDetails;
for didSelectRowAtIndex, I have:
_theObject = _theObjective[rowIndex];
self.theDetails = [[DetailsForWatch alloc] init];
_theDetails.finalObject = _theObject;
I set up the DFW as a Push selection from the Group on IC. When I select a row in the IC, it pushes a blank screen, and the NSLog shows that the PFObject named finalObject is (null). What am I doing wrong that it is not passing on PFObject properly?

There are a couple of ways to pass data between the two interface controllers. The way I have been doing it is like this:
create a segue (give it an identifier if necessary) between the two controllers in my storyboard.
In interface controller 1 implement
- (id)contextForSegueWithIdentifier:(NSString *)segueIdentifier
it will be called when the segue is triggered via button press or whatever.
This can return any object such as a dictionary of the data (in your case 'theDetails')
In interface controller 2 implement
- (void)awakeWithContext:(id)context
the context object here will be the one you passed through in controller 1

Related

Swift accessing and updating tableview in container view

This is kind of confusing but I will do my best to explain. I have a view controller with a container view. In the container view is a table view. I want to update the tableview from the main view controller. For example, the table view will contain a list of names. As the user types in a name into a text field, the table view will update to find names that match what the user inputed.
The main question is:
How can I update the table view from the main view controller?
Note: I can't use prepare for segue because the data will be changing.
I figured it out...
I can access the view through childviewcontrollers. Here's the code I used:
let childView = self.childViewControllers.last as! ViewController
childView.List = self.nameList
childView.tableView.reloadData()
This is actually a beginner question and I would be happy to help. You need to find a place to store your data and then you can access it based on your need. That's what we normally call model.
You can take advantage of one of the design patter: shared instance. It will be existing during the application life cycle. See the following example.
You can have a model class like this:
// .h
#interface DataManager : NSObject
+ (instancetype)sharedManager;
#property (strong, nonatomic, readonly) NSMutableArray *data;
#end
// .m
#interface DataManager : NSObject
#property (strong, nonatomic, readwrite) NSMutableArray *data;
#end
#implementation DataManager
+ (instancetype) sharedManager {
static DataManager *sharedInstance = nil;
static dispatch_once_t dispatchOnce;
dispatch_once(&dispatchOnce, ^{
sharedInstance = [[self alloc] init];
sharedInstance.data = [[NSMutableArray alloc] initWithCapacity:5];
});
return sharedInstance;
}
#end
Using this, you can access your data via your main view controller or your presenting view controller.

Connecting data from different ViewController with same parents

So I have 2 different table views that use the same array (the array is originally created in the Role table view, the below one). How can I connect those two?
(Usually I use prepareForSegue to pass the data but since there is no segue, I'm not sure how can I do this)
EDIT 1: Add the location of the array.
What is a Model and why you need it
In most of the cases it's useless to pass data around if you don't have a Data Model. You can store your data using a technique called Data Persistence.
An example of a pattern you could use is MVC.
MVC or model-view controlelr is an software pattern widely using when making iOS Apps. In this architectural pattern your Controllers are a bridge between your View and your Model.
In this specific scenario both UITableViewControllers would use the same Model but they would display this data differently.
Persisting your Model
There are several ways to do that, the way I like the most is a little framework called CoreData, you can see this question for some reference on that.
You can also refer to this question to see the use of Singletons. But keep in mind that singletons alone do not persist the data. You'll have to add some sort of mechanism if you want the data to remain there between app sessions.
Persisting user preferences
The simplest way to store small chunks of data is using NSUserDefaults (but it's only meant to store defaults):
Let's assume you have an array
NSArray* testArray = #[#"first", #"second", #"third"];
You can set it to a key by using
[[NSUserDefaults standardUserDefaults] setObject:testArray forKey:#"myArray"];
You can sync NSUserDefaults using
[[NSUserDefaults standardUserDefaults] synchronize];
Then, anywhere in your app you can read it doing
[[NSUserDefaults standardUserDefaults] objectForKey:#"myArray"]
Passing data through the app
On the other hand you have to pass your data around somehow. To do so you can use formal protocols, specifically delegates.
As per the Apple documentation:
In a delegate-based model, the view controller defines a protocol for
its delegate to implement. The protocol defines methods that are
called by the view controller in response to specific actions, such as
taps in a Done button. The delegate is then responsible for
implementing these methods. For example, when a presented view
controller finishes its task, it sends a message to the presenting
view controller and that controller dismisses it.
Using delegation to manage interactions with other app objects has key
advantages over other techniques:
The delegate object has the opportunity to validate or incorporate
changes from the view controller.
The use of a delegate promotes
better encapsulation because the view controller does not have to know
anything about the class of the delegate. This enables you to reuse
that view controller in other parts of your app.
For more information on passing data through view controllers (the main point of this question) take a look at this SO answer.
You should never use data persistence just to pass data through the app. Neither user defaults nor core data.
Also using singletons is not good choice. All will mess up your memory.
Instead use call backs — either as delegates or blocks.
Or use unwind segues.
I explain delegates and unwind segues here: Passing row selection between view controllers
this example passes index paths, as it is appropriate in that situation, but the passed object might be of any type or size, as only pointers are passes.
if you use the NSUserDefaults on the other side, data is copied and written to the disk — there for data is copied and slowly processed — without any use.
I created a sample app how to pass data from one view controller to another view controller in another tab bar branch.
click to enlarge
TabBarController
We need to intercept the section of view controllers to set up some callback mechanism. In this case I am using blocks, but delegate would work as-well.
UITabController has a purely optional delegate. I create a subclass of UITabBarController to serv as it's own delegate, but actually a separate delegate should work in the same way.
#import "GameTabBarController.h"
#import "RoleViewController.h"
#interface GameTabBarController () <UITabBarControllerDelegate>
#end
#implementation GameTabBarController
-(void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
}
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navController = (UINavigationController *)viewController;
if ([navController.topViewController isKindOfClass:[RoleViewController class]]) {
RoleViewController *rvc = (RoleViewController *)[navController topViewController];
[rvc setSelectedRole:^(Role *role) {
UIViewController *viewController = self.viewControllers[0];
[viewController setValue:role forKey:#"role"];
[self setSelectedIndex:0];
}];
}
}
return YES;
}
#end
I set the initial tab bar controller to this sub class
Role, RoleDatasource and RoleViewController
The RoleViewController displays a list of Roles, but the datasource and delegate for it's table view are a separate class that I add to the role view controller scene in the storyboard, where i also were it up.
Role
#interface Role : NSObject
#property (nonatomic,copy, readonly) NSString *name;
-(instancetype)initWithName:(NSString *)name;
#end
#import "Role.h"
#interface Role ()
#property (nonatomic,copy) NSString *name;
#end
#implementation Role
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
_name = name;
}
return self;
}
#end
RoleDatasource
#import <UIKit/UIKit.h>
#class Role;
#interface RoleDatasource : NSObject <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, copy) void(^roleSelector)(Role *role);
#end
#import "RoleDatasource.h"
#import "Role.h"
#interface RoleDatasource ()
#property (nonatomic,strong) NSArray *roles;
#end
#implementation RoleDatasource
- (instancetype)init
{
self = [super init];
if (self) {
_roles = #[[[Role alloc] initWithName:#"Magician"], [[Role alloc] initWithName:#"Soldier"], [[Role alloc] initWithName:#"Maid"]];
}
return self;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.roles.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"RoleCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
cell.textLabel.text = [self.roles[indexPath.row] name];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.roleSelector(self.roles[indexPath.row]);
}
#end
RoleViewController
#import <UIKit/UIKit.h>
#class Role;
#interface RoleViewController : UIViewController
#property (nonatomic, copy) void(^selectedRole)(Role *role);
#end
#import "RoleViewController.h"
#import "RoleDatasource.h"
#interface RoleViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#end
#implementation RoleViewController
- (void)viewDidLoad {
[super viewDidLoad];
RoleDatasource *roleDataSource = (RoleDatasource *)[self.tableView dataSource];
[roleDataSource setRoleSelector:^(Role *role) {
self.selectedRole(role);
}];
}
#end
PlayViewController
As soon as a role is selected on the role view controller we want to tell our tab bar controller to switch to the game view controller and show the selected role there, see the code for the tab bar controller.
The GameViewController is just a simple view controller subclass that has a property to hold a role and if a role is set, it will displays it name.
#import <UIKit/UIKit.h>
#class Role;
#interface PlayViewController : UIViewController
#property (nonatomic, strong) Role *role;
#end
#import "PlayViewController.h"
#import "Role.h"
#interface PlayViewController ()
#property (weak, nonatomic) IBOutlet UILabel *roleNameLabel;
#end
#implementation PlayViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.roleNameLabel.text = (self.role) ? self.role.name : self.roleNameLabel.text;
}
#end
You'll find an example on github.
I think that I should put the array in the Tab bar Controller and connect it to the Role Table view (in order to maintain the behaviour like it is before) and connect it to my new Table view to do what I want to do.
The only problem I can think of is that since my program is small, adding this will not be a big problem. But if I have more vc, it's going to be so much pain.

Sending messages from a parent view controller to container views

I have a storyboard in which I have specified a parent view controller and two container views (made up of two UITableViewControllers). In my parent view controller I have buttons that are used to filter the content of the two tables.
My problem is figuring out how to send messages to the container views to perform these filters. I imagine we use delegates but is there a best practice way of implementing these delegates?
Subject to some caveats, you could define properties for each of the two contained tables, connect the outlets in your .xib, and message them directly in your button handlers.
For example:
#interface ParentViewController : UIViewController
#property (nonatomic) IBOutlet Table1Class *table1;
#property (nonatomic) IBOutlet Table2Class *table2;
#end
#implementation ParentViewController
...
- (IBAction)table1FilterButton:(UIButton *)sender
{
[self.table1 filterBy:...];
}
- (IBAction)table2FilterButton:(UIButton *)sender
{
[self.table2 filterBySomethingElse:...];
}
#end
Now, the caveats - you probably won't want to do this if you anticipate that the number of contained view controllers is likely to grow significantly, as it will be unwieldy to have table1, table2, table3, ..., tableN. You'll probably also want to find a way to extract a common interface (in the form of a protocol) from the two contained view controllers, in order to write less divergent code for handling the filtering of each table.
Maybe something like this, instead:
#protocol ContainedTableProtocol
#property (nonatomic) NSPredicate *contentFilterPredicate;
#property (nonatomic) NSComparator sortComparator;
#end
#interface ParentViewController : UIViewController
#property (nonatomic) IBOutlet UITableViewController<ContainedTableProtocol> *table1;
#property (nonatomic) IBOutlet UITableViewController<ContainedTableProtocol> *table2;
#end
#implementation ParentViewController
- (IBAction)filterTable1ButtonAction:(UIButton *)sender
{
[self filterTable:self.table1];
}
- (IBAction)filterTable2ButtonAction:(UIButton *)sender
{
[self filterTable:self.table2];
}
- (void)filterTable:(UITableViewController<ContainedTableProtocol> *)table
{
// Create predicate and comparator as needed...
NSPredicate *predicate = ... ;
NSComparator comparator = ... ;
table.contentFilterPredicate = predicate;
table.sortComparator = comparator;
}
#end
This uses a common interface to apply the filtering operations to each table view controller, and then codes to that interface rather than an API specific to a particular Table1Class or Table2Class.
You can see the answer at How do I create delegates in Objective-C?.
The simpler way is declare the delegate in the Childs and implement in the parent (ie: The childs send data to the parent).

Getting View Controller to retrieve data from a data model file

I'm pretty new to programming. I am creating a very simple iOS Quiz app just for practice. Here is what I have gotten done thus far:
I created the Xcode project using the "Single View" template. Thus, I already have the appDelegate files, the View Controller and a View (XIB file).
My view only has four controllers: 2 UILabels and 2 UIButtons. Each button is paired up with a label. I have all the connections for these 4 controllers setup. When the user taps the button labelled "Get a State" it needs to populate it's label with the name of a state I have in an NSMutableArray called stateArray. When the user taps on the button labelled "Get Capital" is needs to populate it's label with the state's capital in it's label.
I created an Objective-C class that inherits from NSObject to hold my data model called dataModel. In the dataModel.m file I have created and populated the two arrays.
In the view controller .m file I imported the dataModel.h file.
The only problem I am having is getting the view controller to retrieve data from the dataModel file. I have read that I should probably be using Delegation, but I am just looking to know how to do it more simply...I ready something about the view controller and the data model file should have references to each other? If so, what would the coding look like?
Here is my coding thus far:
#import <UIKit/UIKit.h>
#interface onMyOwnViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *stateField;
#property (weak, nonatomic) IBOutlet UILabel *answerField;
- (IBAction)answerButton:(id)sender;
- (IBAction)stateButton:(id)sender;
#end
#import "onMyOwnViewController.h"
#import "dataModel.h"
#implementation onMyOwnViewController
- (IBAction)stateButton:(id)sender
{
NSString *myState = [stateArray objectAtIndex:0]; //this line produces an error.
[_stateField setText:myState];
[_answerField setText:#"hi"];
}
- (IBAction)answerButton:(id)sender
{
}
#end
Below is my dataModel coding:
#import <Foundation/Foundation.h>
#interface dataModel : NSObject
#property(nonatomic, strong) NSMutableArray *answerArray;
#property(nonatomic, strong) NSMutableArray *stateArray;
#end
#import "dataModel.h"
#import "onMyOwnViewController.h"
#implementation dataModel
- (id)init
{
self = [super init];
if(self){
_answerArray = [[NSMutableArray alloc]initWithObjects:#"Michigan", #"Illinios", nil];
_stateArray = [[NSMutableArray alloc]initWithObjects:#"Lansing", #"Springfield",
nil];
}
return self;
}
#end
When I run the app, everything works except retrieving data from the data model. When you replay, please reply with coding.
You need to create an instance of your dataModel in onMyOwnViewController.
#implementation onMyOwnViewController {
dataModel *data;
}
-(void)viewDidLoad {
data = [[dataModel alloc] init];
}
Then in your method call
- (IBAction)stateButton:(id)sender
{
NSString *myState = data.stateArray[0];
...
}

Unable to set custom protocol delegate using ARC with two UITableViewControllers using UINavigationController

I'm trying to set the delegate for my custom protocol that has one required method allowing me to pass an array of objects back in the hierarchy of two UITableViewControllers. My delegate continues to return nil. Due to this, my required method is never called.
I'm wondering if the datasource and delegate implementations with my UITableViewControllers is causing a conflict. Also, perhaps ARC is getting in the way when declaring the delegate?
It should be noted that both UITableViewControllers were built using Storyboard and are navigated using segues within a UINavigationController (not sure if this may be causing issues or not).
The nav is --> AlarmViewController --> AlarmDetailsViewController. I create an Alarm object in my AlarmDetailsViewController that contains all the details for an alarm, place it into an array and I want to pass that array back to my AlarmViewController to be displayed in a custom cell in the table.
NOTE: I want to use the Delegate pattern here. I'm not interested in solutions that invoke NSNotifications or use my AppDelegate class.
AlarmDetailsViewController.h
#import "Alarm.h"
#protocol PassAlarmArray <NSObject>
#required
-(void) passAlarmsArray:(NSMutableArray *)theAlarmsArray;
#end
#interface AlarmDetailsViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
{
//.....
id <PassAlarmArray> passAlarmsArrayDelegate;
}
#property (nonatomic, retain) id <PassAlarmArray> passAlarmsArrayDelegate;
#end
AlarmDetailsViewController.m
#import "AlarmDetailsViewController.h"
#interface AlarmDetailsViewController ()
#end
#implementation AlarmDetailsViewController
#synthesize passAlarmsArrayDelegate;
-(void) viewWillDisappear:(BOOL)animated
{
NSLog(#"delegate = %#", self.passAlarmsArrayDelegate); // This prints nil
[[self passAlarmsArrayDelegate] passAlarmsArray:alarmsArray];
}
//....
#end
AlarmViewController.h
#interface AlarmViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate, PassAlarmArray>
{
//...
AlarmDetailsViewController *alarmDetailsViewController;
}
#property (nonatomic, retain) AlarmDetailsViewController *alarmDetailsViewController;
#end
AlarmViewController.m
#import "AlarmViewController.h"
#import "AlarmDetailsViewController.h"
#import "AlarmTableViewCell.h"
#import "Alarm.h"
#interface AlarmViewController ()
#end
#implementation AlarmViewController
#synthesize alarmDetailsViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// This is where I'm attempting to set the delegate
alarmDetailsViewController = [[AlarmDetailsViewController alloc]init];
[alarmDetailsViewController setPassAlarmsArrayDelegate:self];
}
//....
//My #required protocol method which never gets called since my delegate is nil
-(void) passAlarmsArray:(NSMutableArray *)theAlarmsArray
{
alarmsTableArray = theAlarmsArray;
NSLog(#"alarmsTableArray contains: %#", alarmsTableArray); // Never gets called due to delegate being nil
NSLog(#"theAlarmsArray contains: %#", theAlarmsArray); // Never gets called due to delegate being nil
}
#end
I've attempted to set the delegate in a method that fires when a button is pressed in AlarmViewController (as opposed to the viewDidLoad method) but that does not work either.
I'm assuming I've got a logic flow error somewhere here . . . but nearly 2 days of hunting and rebuilds haven't uncovered it. Ugh.
You're setting your delegate in the wrong place, and on a different instance of the controller than the one you will get when you do the segue. You should set the delegate in the prepareForSegue method if you're pushing AlarmDetailsViewController from AlarmViewController
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
AlarmDetailsViewController *alarm = segue.destinationViewController;
alarm.passAlarmsArrayDelegate = self;
}
You really need to understand the life cycle of view controllers, how and when they're instantiated, and when they go away. This is the very heart of iOS programming, and Apple has extensive documentation on it. Reading up on segues would also be very useful. A segue (other then an unwind segue) always instantiates a new instance of the destination controller. So, when your segue is performed, whether directly from a button, or in code, a new (different from the one you alloc init'd directly) details controller is instantiated. Before that segue is performed, prepareForSegue: is called, and that's when you have access to the one about to be created. That's the place to set a delegate or pass any information on to the destination view controller.
Did you try replace (nonatomic, retain) with (nonatomic, strong) since you are using ARC?
Auto-synthesized properties like your alarmDetailsViewController property have backing ivars prefixed with underscores, e.g. _alarmDetailsViewController. Your alarmDetailsViewController ivar (the alarmDetailsViewController declared inside the #interface ... {} block in AlarmViewController.h) is different from the backing ivar of your alarmDetailsViewController property.
Just delete your alarmDetailsViewController ivar and use the #property, preferably through self.alarmDetailsViewController.

Resources