Page number in Page-Based Interface with WatchKit (Watch)? - xcode

I created a WatchKit app with a Page-Based Interface.
There are 3 pages and each one is connected to my InterfaceController.swift class (which extends WKInterfaceController).
My question: inside InterfaceController.swift how can I detect the page number of the current view?

If you use
func presentControllerWithNames(_ names: [AnyObject],
contexts contexts: [AnyObject]?)
You just have to pass the number of the page in the context, so you can store it, and retrieve it later on. This is the non storyboard way.
If you use storyboard, it is the same, you just have to use the other method
func contextsForSegueWithIdentifier(_:inTable:rowIndex:)
And pass your page index in the context of each controller

It can be achieved this way,
Present InterfaceController in pageNavigation with Appropriate Context Values,
[self presentControllerWithNames:#[#"TestInterfaceController",#"TestInterfaceController",#"TestInterfaceController"] contexts:#[#"Page 1",#"Page 2",#"Page 3"]];
Then create a NSString property to track page and label to display it,
#property (nonatomic, strong) NSString *currentContext;
#property (weak, nonatomic) IBOutlet WKInterfaceLabel *pageLabel;
In awakeWithContext assign it to NSString property,
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
NSLog(#"%#",context);
self.currentContext = context;
}
Display it on willActive,
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
[super willActivate];
NSLog(#"%# willActivate",self.currentContext);
[self.pageLabel setText:self.currentContext];
}
You can also detect when page didDeactivate,
- (void)didDeactivate {
// This method is called when watch view controller is no longer visible
[super didDeactivate];
NSLog(#"%# didDeactivate",self.currentContext);
}
Edit :
If page navigations are configured using Storyboard Segue then override this method in Source IntefaceController from where you created model segue to destination controller to provide contexts,
- (NSArray *)contextsForSegueWithIdentifier:(NSString *)segueIdentifier {
NSArray *contexts = nil;
if ([segueIdentifier isEqualToString:#"MyPageNavigation"]) {
contexts = #[#"Page 1",#"Page 2",#"Page 3"];
}
return contexts;
}

Related

The new UISplitViewController in iOS8 using objective c without storyboard

I try to implement adaptive UI in my app. By making UISplitViewController as the rootview controller, I can run the iPhone's code in iPad too.
I red Apple's documentation about UISplitViewController and some samples. All are using storyboards and the sample codes are available in swift only. I can not find a working version of code. So I started the code myself.
See my splitview controller class (BaseSplitViewController)
BaseSplitViewController.h:
#import <UIKit/UIKit.h>
#interface BaseSplitViewController : UISplitViewController <UISplitViewControllerDelegate>
#end
BaseSplitViewController.m:
#import "BaseSplitViewController.h"
#import "TabBarViewController.h"
#interface BaseSplitViewController ()
#property(nonatomic, strong) TabBarViewController *primaryTabBarVC;
#property(nonatomic, strong) UINavigationController *primaryNavigationController;
#property(nonatomic, strong) UINavigationController *secondaryNavigationController;
#end
#implementation BaseSplitViewController
- (instancetype)init
{
self = [super init];
if (self)
{
[self setViewControllers:#[self.primaryNavigationController, self.secondaryNavigationController]];
self.delegate = self;
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(cellTapped:) name:#"cellTapped" object:nil];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self assignPrimaryViewController];
}
- (void)assignPrimaryViewController
{
// Need to assign tab bar controller as primary view controller here
}
- (void)assignSecondaryViewController:(UIViewController *)vc
{
// Need to update the secondary controller each time the primary controller was tapped
}
- (UINavigationController *)primaryNavigationController
{
if (!_primaryNavigationController)
{
_primaryNavigationController = [[UINavigationController alloc] init];
}
return _primaryNavigationController;
}
- (UINavigationController *)secondaryNavigationController
{
if (!_secondaryNavigationController)
{
_secondaryNavigationController = [[UINavigationController alloc] init];
}
return _secondaryNavigationController;
}
- (UITabBarController *)primaryTabBarVC
{
if (!_primaryTabBarVC)
{
_primaryTabBarVC = [[TabBarViewController alloc] init];
}
return _primaryTabBarVC;
}
#end
Some points:
The above class "BaseSplitViewController" is the rootview controller of my app.
That is, self.window.rootViewController = [[BaseSplitViewController alloc] init];
From Apple's Documentation,
"When designing your split view interface, it is best to install
primary and secondary view controllers that do not change. A common
technique is to install navigation controllers in both positions and
then push and pop new content as needed. Having these types of anchor
view controllers makes it easier to focus on your content and let the
split view controller apply its default behavior to the overall
interface."
So, I created two navigation controllers (primary/secondary) and set them as split view controllers's primary & secondary views. setViewControllers: can be used for this.
My primary view here is, tab bar view. So, inside the assignPrimaryViewController: method, I should assign my TabBarViewController as split view controller's primary view.
Here, I found two ways.
1. [self.primaryNavigationController showViewController:self.primaryTabBarVC sender:nil];
2. [self.primaryNavigationController pushViewController:self.primaryTabBarVC animated:YES];
Here, I tried with [self showViewController:self.primaryTabBarVC sender:nil]; but my tab bar view was never shown. From my understanding, here "self" means the UISplitViewController. Calling showViewController: here makes the confusion to choose the navigation controller. Because we have two navigation controllers. So we need to clearly tell that navigation controller which needs to hold the primary controller.
Primary view controller part is over. Now the real problem starts. Consider my primary view controller is the tab bar which have tableview's in it. If I tap on the cell, I need to update the secondary view's content. This is the case in Regular mode. In compact mode, I expect when the user taps on the cell, it should push the detail view (secondary view) with back button.
I expect to put the below code within assignSecondaryViewController: vc: method
[self.secondaryNavigationController pushViewController:vc animated:NO];
[self.primaryNavigationController showDetailViewController:self.secondaryNavigationController sender:nil];
But it does not works.
Questions:
What should be placed inside assignPrimaryViewController & assignSecondaryViewController: methods to get my expected result?
And I really, yes really don't know how to implement UISplitViewController's following delegate methods.
primaryViewControllerForCollapsingSplitViewController:
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
primaryViewControllerForExpandingSplitViewController:
splitViewController:separateSecondaryViewControllerFromPrimaryViewController:
Would be really helpful, if someone explains this new UISplitViewController's behavior.
Thanks

ipad uisplitview: passing data from masterviewcontroller to a uiviewcontroller (redirected via segue)

I'm new to iOS programming so I want to make this thing from scratch to be able understand how the whole thing works. So instead of using the master detail template I did this from the ground up.
I'm having a huge roadblock in terms of passing data between the master view and the detail view. At the moment, whenever I tap on the item of the master view it would look for the first view on the detail view controller, let's call it mainswitchviewcontroller. It's the first ViewController connected via segue to the UINavigationController for the detail controller part (Please refer to image below).
This happens after login where it shows that viewcontroller, I want the user to pick an option before accessing the whole app. which will lead them to the last ViewController of the flow (refer to the image below - red circle).
Now what I'm trying to do is this:
#interface MasterListViewController : UITableViewController{
}
#property (nonatomic) int matNum;
#property (strong, nonatomic) DetailStaffMainContentViewController *detailViewController;
#end
:
:
#implementation MasterListViewController
#synthesize matNum;
:
:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
for(UIViewController *vc in self.splitViewController.viewControllers){
NSLog(#"%#)", vc.title);
}
self.detailViewController.teststring = #"gronk";
}
This is my detailViewController (the one I want to access)
#implementation DetailStaffMainContentViewController
#synthesize tableView;
#synthesize teststring;
#synthesize lblOutput;
Now on select of the row, I assign the teststring which I want to use to populate that label on the controller. That's what I did.
My questions are:
I'm getting an error. Wondering what I was doing wrong?
[MainSwitchBoardViewController setTeststring:]: unrecognized selector
sent to instance 0x7572190
Is this a recommended way of passing data? Or should I just stick with notificationcenters?
Thoughts?
EDIT
Just to give you on the flow of the app:
User logs in (if username does not exist, the floating viewcontroller will pop up and force the user to log in.
Once the user logs in, the pop up goes away and then asks the user what type of role is to be used in the current session (eg. staff or fighter).
If the user picks fighter, it pushes the user to the top VC If the user picks staff, it pushes the user to the bottom VC (collections controller)
FOR Staff: At the moment I'm pushing to the last VC with this [self perfermSegueWithIdentifier:sender:self]
Once the user gets shown the last VC (DetailsStaffView), I want this to be the main Details View where the user does it's transaction. A few more pop ups? (Or maybe another push to another VC for specific transaction needs to be added). But ultimately I don't want the user to access the first Viewcontroller (MasterSwitchBoard)
EDIT 2
This is the main switch board class that controls the first VC after login.
#interface MainSwitchBoardViewController : UIViewController{
}
#property (strong, nonatomic) IBOutlet UILabel *txtLabel;
- (IBAction)btnStaff:(id)sender;
- (IBAction)btnAdmin:(id)sender;
#end
This is the collections VC which atm automatcially pushes to the last VC. In the beginning I was using a notification on this but it seems like it's not necessary so I took it out.
#interface MatListCollectionViewController ()
#end
#implementation MatListCollectionViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self performSegueWithIdentifier:#"SeguePushToStaffDetailFromMats" sender:self];
}
And then the details staff
#interface DetailStaffMainContentViewController : UIViewController{
}
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#property (nonatomic, retain) IBOutlet UILabel *lblOutput;
#property (nonatomic, retain) NSString *teststring;
#end
#implementation DetailStaffMainContentViewController
#synthesize tableView;
#synthesize teststring;
#synthesize lblOutput;
I'll tackle those in reverse order:
Is this a recommended way of passing data? Or should I just stick with notificationcenters?
You theoretically could use notification center, but I wouldn't in this case. Personally, I only use NSNotificationCenter if:
I'm doing asynchronous process which has no reasonable way of knowing the state of the view controllers (e.g. I have some background task that's doing some time consuming update of data from a server, and the user could have navigated to just about anywhere in the app by the time I'm ready to notify the view controllers that the update is done); and/or
I potentially have multiple objects that I want to notify of some event.
Neither of those conditions apply here. Certainly you could use NSNotificationCenter, but that should be unnecessary. Explicitly setting properties is generally preferable.
I'm getting an error. Wondering what I was doing wrong?
Unfortunately, it's hard to say on the basis of the evidence you've presented thus far. But there are a few things that I'm having trouble reconciling on the basis of your code and comments:
You've declared detailViewController to be a DetailStaffMainContentViewController (and that's the class for which you've synthesized the teststring accessors);
But your error message suggests that detailViewController is clearly a MainSwitchBoardViewController object (which doesn't understand the setTeststring accessor method); and
You haven't shown us how you're setting detailViewController, so it's hard to say where you've gone wrong here.
Personally, in cases like this, I'm always reticent to define properties to maintain pointers to view controllers. I'd generally be inclined to ask the UISplitViewController what's in the detail split. So, rather than having a class property/ivar for that, in my master view controller, I have a method like:
- (UIViewController *)detailViewController
{
return [[self.splitViewController.viewControllers lastObject] topViewController];
}
That's basically saying "get the last object from my split view controller's array of viewControllers (the detail split) and because I (personally) always use a navigation controller in that detail split, let's use the topViewController to get a pointer to my subclassed UIViewController that's in that detail split (embedded within the navigation controller).
Then I have my didSelectRowAtIndexPath check to see if that view controller is the one I expected. If not, I segue to it (and have prepareSegue pass the data I want to that controller).
So, for example, consider this storyboard:
Here, "A" is my default detail controller. "B" might be some random scene that I may have replaced "A" with. And "C" is my real detail view controller where I can see the details for the item I selected from the master view controller's table view. Thus, my didSelectRowAtIndexPath for the master view controller might look like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIViewController *controller = [self detailViewController];
if (![controller isKindOfClass:[DetailCViewController class]])
{
// if I'm not already showing "C" in my detail split, then let's segue to it
[self performSegueWithIdentifier:#"GoToC" sender:self];
}
else
{
// if I'm already showing "C" in my detail split, let's just set the item of data I want to pass to it
DetailCViewController *cController = (DetailCViewController *)controller;
cController.detailItem = self.objects[indexPath.row];
}
// I have a method I call to make sure that the popover menu for the master view
// controller disappears (in case it popped up since I was in portrait mode) and
// adds the master view controller button to the navigation bar if I need it.
[self removePopoverAndAddNavigationButton];
}
In case I needed to segue to "C", I also have my `prepareSegue pass the detailItem as needed:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"GoToC"])
{
DetailCViewController *cController = (DetailCViewController *)[segue.destinationViewController topViewController];
cController.detailItem = self.objects[[[self.tableView indexPathForSelectedRow] row]];
}
}
As I said, trying to figure out why your detailViewController has the wrong type of object is hard to say, but this is how I might tackle something like this.
Maybe you should first take a look at some nice movies from the last WWDC where they describe Storyboard Segues.
In the code you provided there is nothing written about segues. Be sure to use segues either via IB or programmatically via [self performSegueWithIdentifier:]. See Apple Docs
Then you can provide additional operations in yout view controller:
[view shouldPerformSegueWithIdentifier:sender:]
[view prepareForSegue:sender:]
The first one decides whether to perform the segue or not. The second one is executed just before segue is performed. Within the secon operation you can provide information to the destinationViewController.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"showIssuesByProject"]){
if (_searchActive) {
((IssueListViewController *)segue.destinationViewController).project = [_filteredProjects projectAtIndex:[self.tableView indexPathForSelectedRow].row];
} else {
((IssueListViewController *)segue.destinationViewController).project = [_projects projectAtIndex:[self.tableView indexPathForSelectedRow].row];
}
}
if ([segue.identifier isEqualToString:#"showIssuesByFilter"]) {
((IssueListViewController *)segue.destinationViewController).filter = [_filters filterAtIndex:[self.tableView indexPathForSelectedRow].row];
}
}
Be careful: UISplitViewControll.viewControllers are only two controllers: the master and detail ones.

Xcode best way to re-use area/view for multiple forms

I'm new to Xcode and starting a universal app. In the iPad version I want to re-use the main section for different search forms.
I want the user to see a completely different form in the section to the right (see image) for each button on the left menu.
I'm working programmatically.
What's the best way to change the content in this situation? (keeping in mind performance and coding complexity)
EDIT: This is a standard tabbed application, not a split view. Could I have a new view controller and .xib for each form and then change which one is displayed by embedding them in a UIView or UIWindow on this screen?
You've hit upon the right answer in your edit. From what I understand the buttons on the side are analagous to another tab bar controller, but nested within your main tab bar controller?
You might as well follow the same pattern, since it is a familiar one. Your search view controller should act as a container view controller, which would have an array of view controllers representing each form option.
As you switch between the options, add/remove the appropriate view controller views from your view hierarchy (using addSubview) and view controller hierarchy (using the code in "Adding and removing a child" in the link). The view controller hierarchy is important to ensure that viewDidAppear and so forth is called on your child controllers.
As an illustration, I've created a simple demo project where the main view controller has a set of buttons, each linked to the same action, and an array of contained view controllers. The contained view controllers will be held inside a subview, called container in this example. This would be the size of the right hand area in your screenshot above.
The child controllers are set up as follows in viewDidLoad of the container view controller:
#property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *buttons;
#property (weak, nonatomic) IBOutlet UIView *container;
#property (nonatomic,strong) NSArray *viewControllers;
#property (nonatomic, strong) UIViewController *currentChild;
#end
#implementation JRTViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
for (UIButton *button in self.buttons)
[button addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
NSMutableArray *children = [NSMutableArray new];
for (int i = 0; i < 4; i++)
{
ContainedViewController *child = [ContainedViewController new];
child.name = [NSString stringWithFormat:#"Form %d",i + 1];
[children addObject:child];
}
self.viewControllers = [children copy];
[self buttonTapped:self.buttons[0]];
}
Here I've used four instances of the same view controller class - all it has is a label which indicates which form you are selecting. Really you'd have different classes for each one. I've also "selected" the initial view controller by sending the action method for the first button. The action method does this:
- (IBAction)buttonTapped:(UIButton *)sender
{
NSUInteger index = [self.buttons indexOfObject:sender];
if (index != NSNotFound)
self.currentChild = self.viewControllers[index];
}
Which selects the appropriate VC from the view controller array. The setter method does this:
-(void)setCurrentChild:(UIViewController *)currentChild
{
if (currentChild == _currentChild) return;
// Remove the old child
[_currentChild willMoveToParentViewController:nil];
[_currentChild.view removeFromSuperview];
[_currentChild removeFromParentViewController];
[_currentChild didMoveToParentViewController:nil];
// Add the new one
[currentChild willMoveToParentViewController:self];
[self.container addSubview:currentChild.view];
NSDictionary *views = #{#"view":currentChild.view};
[self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[view]|" options:0 metrics:nil views:views]];
[self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:nil views:views]];
[self addChildViewController:currentChild];
[currentChild didMoveToParentViewController:self];
_currentChild = currentChild;
}
Which sets up the view controller hierarchy, adds in the view and uses constraints to make it fill the container.
In this example I've hardcoded in four buttons and four child view controllers as I was demonstrating the addition and switching of child view controllers. In reality you'd make it more like a tab bar controller where you assign an array of view controllers and the container would make its own array of buttons.

iOS Master-Detail app: defining protocol, delegates

In the master-detail application template (using ARC, storyboards) in XCode 4.3.2, I am trying to change (more specifically replace) the detail view when an item in master table view is selected. I am trying to implement delegates/protocols for this.
What I am confused about is - which class should implement the methods defined in protocol - master or detail?
Having the detail view implement the protocol method makes sense to me since, I'll be push/popping the view controllers in detail view based on the selection (passed as a string from master via the protocol method).
Here's what I tried
1) Defined the protocol in MasterViewController.h
#protocol MasterViewDelegate <NSObject>
- (void)masterSelectionChanged:(NSString *)selection;
#end
#interface MasterViewController:UIViewContoller
#property (nonatomic, weak) id <MasterViewDelegate> delegate
2) in MasterViewController.m
#synthesize delegate;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[delegate masterSelectionChanged:#"Some string based on indexPath.row"];
}
3) in DetailViewController.h
#import "MasterViewController.m"
#interface DetailViewController:UINavigationController <MasterViewDelegate>
#end
4) in DetailViewController.m
#pragma mark - MasterViewDelegate
- (void)masterSelectionChanged:(NSString *)selection
{
NSLog(#"the selection is: %s", selection);
// WIll push/pop view over here, may be perform segues based on selection
}
In this process, upon selecting the rows in master table, nothing happened. No crash, no display of log, no error while building either. What did I miss over here?
You need to set the delegate property - at the moment it will be nil so nothing happens when you send messages to it. In the iPad template you can do this as follows, in the viewDidLoad of your detail view controller:
[super viewDidLoad];
if (self.splitViewController) // Means this won't be called if you use this code on iPhone too.
{
// According to comments your master controller is embedded in a nav controller
UINavigationController *nav = (UINavigationController*)[self.splitViewController.viewControllers objectAtIndex:0];
// I am assuming it is the root view controller
MasterViewController *master = (MasterViewController*)nav.rootViewController;
// Finally set the delegate
master.delegate = self;
}

Change IBOutlet text to property of object returned from custom class

I'm using xCode 4.2.1 - I have an app that uses a web service and fetches some data using a custom class and NSURLConnection. The user taps a "refresh" button which starts a series of events that happen in some custom classes in my project, and I can get the object, along with the properties I want to return in a method in the MainViewController, I'm just not able to change the text of an IBOutlet to a property (NSString) of the returned object.
In my "MainViewController.h, I have an IBOutlet (and it's wired to a button in the MainViewController.xib):
IBOutlet UILabel *textLabel;
and:
#property (nonatomic, retain) IBOutlet UILabel *textLabel;
And I've synthesized the label in MainViewController.m
#synthesize textLabel;
The process looks like this: Refresh Button Tapped --> Fires a mthod in MainViewController --> Fires another method in a custom class (retrieves data from web, creates object) --> Sends Object to MainViewController via: (in my custom class implementation)
// parsing, etc.. and define a string for priceString property of mp object
MainViewController *mvc = [[MainViewController alloc] init];
[mvc log:mp];
And in my MainViewController.m I can access a property of that object and print it in NSLog just fine from this method.
- (void) log:(Price *)mp {
self.textLabel.text = mp.priceString;
NSLog(#"%#", mp.priceString);
}
At this point, I can see the data in the log, but the textLabel text won't change.
I've been trying to read examples for a week, and I've heard everything from delegation, to NSNotication answers, but nothing seems to work.
All I need to do is populate an IBOutlet from a -(void) method.
Any help would be greatly appreciated, and I'm down for implementing delegation, but I'm very new to it, and seeking an example.
EDIT - more details.
After further research, I think I should note that my NSURLConnection that is returning my objects is in a separate class, and I've been reading a lot of threads where people are starting the connection in viewDidLoad.
The problem is that you are calling [mvc log:mp]; just after initializing the view controller. At that stage, the view is still not loaded from the xib file.
you can change log to see that:
- (void) log:(Price *)mp {
if (self.textLabel) {
self.textLabel.text = mp.priceString;
} else {
NSLog(#"textLabel is nil.");
}
}
You can call [mvc log:mp]; in the view controller viewDidLoad method, or after viewDidLoad has been executed.
To understand this better, add NSLog(#"viewDidLoad."); in viewDidLoad method, and add NSLog(#"viewController initialized."); just after the code where you initialized the view controller.
You will see the following
viewController initialized.
textLabel is nil. // if you call log as usual after initializing the view controller.
viewDidLoad.

Resources