I'm having some trouble understanding how all the "nuts and bolts" of the Master-Detail Application template works using Xcode 4.2 (without MainWindow.xib, as well as other changes). In "AppDelegate" we have the following code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
MasterViewController *masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
UINavigationController *masterNavigationController = [[UINavigationController alloc] initWithRootViewController:masterViewController];
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
UINavigationController *detailNavigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
self.splitViewController = [[UISplitViewController alloc] init];
self.splitViewController.delegate = detailViewController;
self.splitViewController.viewControllers = [NSArray arrayWithObjects:masterNavigationController, detailNavigationController, nil];
self.window.rootViewController = self.splitViewController;
[self.window makeKeyAndVisible];
return YES;
}
I see that the window is created programmatically instead of using the MainWindow.xib that was use in previous versions of Xcode in the beginning, as well as instantiating objects for both "Master" and "Detail" view controller classes that are provided with the template and using them for separate UINavigationControllers. Then the splitViewController property is assigned a new allocated UISplitViewController object assigning the detailViewController as the "delegate" and an array is created that contains both UINavigationControllers as "viewControllers." Then the window.rootViewController is assigned this splitViewController object.
The main questions I have are
1) Why do I need two "UINavigationControllers?" Couldn't I just create the "viewController" array using the "master" and "detail" view controllers themselves?"
2) What does it do setting the "detailViewController" as the "delegate?" What actually gets delegated?
3) And finally, if I wanted to push additional items onto the "DetailViewController" stack, would I just use the "DetailViewController" class to push using the "didSelectRow.." method, or would I need to do updates to self.splitViewController.viewControllers property instead?
1) Why do I need two "UINavigationControllers?" Couldn't I just create the "viewController" array using the "master" and "detail" view controllers themselves?"
You don't need. But it's a way. You have the ability to push the masterViewControlleras well as the detailViewController. Look at the layout in Storyboard. With using segue you can change the controllers on each side as you like.
2) What does it do setting the "detailViewController" as the "delegate?" What actually gets delegated?
The UISplitViewget's delegated. The detailViewController will take care of the interface changing in portrait and landscape mode. See UISplitViewDelegate in the documentation.
Showing and Hiding View Controllers
– splitViewController:shouldHideViewController:inOrientation:
– splitViewController:willHideViewController:withBarButtonItem:forPopoverController:
– splitViewController:willShowViewController:invalidatingBarButtonItem:
– splitViewController:popoverController:willPresentViewController:
3) And finally, if I wanted to push additional items onto the "DetailViewController" stack, would I just use the "DetailViewController" class to push using the "didSelectRow.." method, or would I need to do updates to self.splitViewController.viewControllers property instead?
Yes you can push in the masterViewControllerwith the tableView selection. You can push either with the new controller on the masterViewControllerstack by pushing in the
- (void)viewDidAppear:(BOOL)animated
and you can push in any way you like. You don't have to update the self.splitViewController.viewControllers property. Maybe you have to set the delegate to your new detailViewController.
Related
I'm trying to create a split view app with multiple detail view controllers. I'm having trouble with the basic setup/skeleton of the app. First I tried using xcode's Master-Detail Application template. The problem is that the classes from the template look something like this:
MasterViewController.h
MasterViewController.m
DetailViewController.h
DetailViewController.m
But what I want is something like this:
MasterViewController.h
MasterViewController.m
TitleViewController.h
TitleViewController.m
DateViewController.h
DateViewController.m
...
I can't figure out how to get my view controllers to load when user selects a new row.
I also tried using the sample MultipleDetailViews app from Apple, but the sample app has multiple issues for me including the fact that it uses nib files, which I don't want.
Can anyone help? Is there some tutorial about how to set up a split view app with multiple detail view controllers (without nibs)? Thank you!
*response:
Thanks! Could you post the link for the BigNerdRanch one? I couldn't find it. Also, I couldn't follow the Raywenderlich one because it uses an older version of xcode. Following your sample project, I think I'm close, but I'm getting a "terminating with uncaught exeption...". Here's what I did:
Create new project from Master-Detail template.
Add files MyTableViewController.h and MyTableViewController.m.
In MasterViewController.m, set the number of sections to 1 and the number of rows to 2.
In MasterViewController.h add property "MyTableViewController *myTableViewController".
(When user clicks 1st row, detailViewController should show, when he clicks 2nd row, myTableViewController should show.)
In MasterViewController.m, change didSelectRowAtIndexPath to:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = [indexPath row];
if( row == 0 ) {
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.detailViewController];
NSArray *vcs = [NSArray arrayWithObjects:[self navigationController],nav, nil];
[[self splitViewController] setViewControllers:vcs];
}
else {
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.myTableViewController];
NSArray *vcs = [NSArray arrayWithObjects:[self navigationController],nav, nil];
[[self splitViewController] setViewControllers:vcs];
}
}
The project runs, but when I click on the 2nd row (row == 1) I get the exception.
*update 2
- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath {
int row = [indexPath row];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
UINavigationController *detailNav = [delegate.splitController.viewControllers objectAtIndex: 1];
NSArray *viewControllers = nil;
switch (row) {
case 0:
viewControllers = [[NSArray alloc] initWithObjects: delegate.dateController, nil];
break;
case 1:
viewControllers = [[NSArray alloc] initWithObjects: delegate.repeatController, nil];
default:
break;
}
[delegate.splitController removeFromParentViewController];
detailNav.viewControllers = viewControllers;
[delegate.window addSubview: delegate.splitController.view];
}
There's actually a great tutorial by the BigNerdRanch on how to use UISplitViewControllers. Honestly, there's no difference between using them to push a different UIViewController as the Detail or Master viewController. They just need a reference to one another and when an event happens, you push a new viewController to one side or the other. I've attached a sample project below for you to reference.
sample project: link
Here's another tutorial on setting one up via Raywenderlich: link
So the following happenins inside a UITableViewController which is the MasterViewController.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 1. get the controllers from the split view [master, detail]
let controllers = split.viewControllers
// 2. get nav controller for the detail + the current storyboard
let navigationController = controllers[controllers.count-1] as!
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// 3. create the appropriate view controller (vc) then just replace the navigation controllers root vc with your new one using 'navigationController.setViewControllers([vc] ...'
switch indexPath.row {
case 1:
let vc = storyboard.instantiateViewController(withIdentifier: "DetailTableViewController") as! DetailTableViewController
navigationController.setViewControllers([vc], animated: false)
case 2:
let vc = storyboard.instantiateViewController(withIdentifier: "DetailCollectionViewController") as! DetailCollectionViewController
navigationController.setViewControllers([vc], animated: false)
default:
let vc = storyboard.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
navigationController.setViewControllers([vc], animated: false)
break
}
}
You can do the same thing with segues. From the MasterViewController in the storyboard, drag a segue to a navigation controller and set the segue kind as a 'Show Detail (e.g. replace)'. Then from there in you didSelectRowAt you can call performSegue with identifier. Make sure that your segue has an 'Identifier'
Have a look at this video
I'm trying to find an updated doc that includes any info / code samples on the new interactive push notifications. The guide I found on Local & Remote Push Notifications still shows the payload size is 256 bytes. My understanding is that in ios8 that limit has been raised to 2k.
I'm also trying to find documentation on how to add custom buttons to make my notifciations interactive. I don't see very much in the push notification programming guide.
How do I setup a category to add custom buttons with colors? Any documentation on this would be useful.
You can create interactive notifications by defining the action buttons in iOS8.
Create UIMutableUserNotificationAction buttons.
Then Create UIMutableUserNotificationCategory and group above actions into category.
Add all categories into set.
Create UIUserNotificationSettings with this category set.
Register notifications with this settings
Add category field in push payload and send notification
Please find below the sample code:
- (void) registerRemoteNotificationWithActions{
//1. Create action buttons..:)
UIMutableUserNotificationAction *shareAction = [[UIMutableUserNotificationAction alloc] init];
shareAction.identifier = #"SHARE_IDENTIFIER";
shareAction.title = #"Share";
shareAction.activationMode = UIUserNotificationActivationModeForeground;
shareAction.destructive = NO;
shareAction.authenticationRequired = YES;
//2. Then create the category to group actions.:)
UIMutableUserNotificationCategory *shareCategory = [[UIMutableUserNotificationCategory alloc] init];
shareCategory.identifier = #"SHARE_CATEGORY";
[shareCategory setActions:#[shareAction] forContext:UIUserNotificationActionContextDefault];
[shareCategory setActions:#[shareAction] forContext:UIUserNotificationActionContextMinimal];
//3. Then add categories into one set..:)
NSSet *categories = [NSSet setWithObjects:shareCategory, nil];
//4. Finally register remote notification with this action categories..:)
UIUserNotificationSettings* notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
}
Sample payload format :
{
"aps": {
"badge": 1,
"alert": "Hello world!",
“category”: “SHARE_CATEGORY”
}
}
And handle the actions using the following method :
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void(^)())completionHandler
{
if ([identifier isEqualToString:#"SHARE_IDENTIFIER"] ){
}
}
You can check this link for more info.
This is a tutorial I found in youtube for custom action push notification. It is done in swift.
https://www.youtube.com/watch?v=Yh3lLpV1k_Y
I am building an iOS7 app and I am trying to make use of the new useLayoutToLayoutNavigationTransitions effect. I have 2 UICollectionViewControllers and when I do the following I get the transition effect
SecondViewController *secondVC = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
secondVC.useLayoutToLayoutNavigationTransitions = YES;
[self.navigationController pushViewController:secondVC animated:YES];
this works fine but what I want to do is make an api call and then in the completion block I want to push onto the nav stack like so
[webAPI getDetailsWithParams:params andCompletionBlock:^(id dict) {
//some work on the result
SecondViewController *secondVC = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
secondVC.useLayoutToLayoutNavigationTransitions = YES;
[self.navigationController pushViewController:secondVC animated:YES];
} andErrorBlock:^(NSError *error) {
}];
but this crashes every time with the following msg
-[UICollectionView _invalidateLayoutWithContext:]: message sent to deallocated instance 0x17a26400
can anyone tell me what I am doing wrong in this case? How can I get the transition effect when pushing from completion block?
EDIT: by changing it to the following I was able to transition to the second viewcontroller.
MyLayout *layout = [[MyLayout alloc] init];
SecondViewController *expandedVC = [[SecondViewController alloc] initWithCollectionViewLayout:layout];
and I also deleted the nib file that went with the file. nib file just consisted of a collection view and it was the file owners view.
While I can now transition I still do not understand why I could not do the previous nib method with in a block. So I would be grateful if someone could shed some light on it for me.
In order to use UICollectionViewController's useLayoutToLayoutNavigationTransitions to make transitions, the layout of the SecondViewController must be known. However, if you use initWithNibName:bundle: initializer, layout is not internally prepared yet, making your desired transitions impossible. As you mentioned in your edited question, you have to use [UICollectionViewController initWithCollectionViewLayout:] to initialize your second UICollectionViewController. Since your xib file has the same name as your class name, SecondViewController.xib is going to be loaded automatically by UIViewController, superclass of UICollectionViewController.
I think you were making UIKit calls from a thread that wasn't the main thread
I search Google to find a good answer but the most tutorials are shown in previous Xcode Versions...
Also, I don't want to drag-n-drop cells from the Interface Builder, but to control the Table View programmatically (from an NSObject subclass file).
What I currently do is this: 1. Create a file named tableController.h that is a subclass of NSObject.
2. I create an NSObject Object in my Nib File (and set it as a subclass of tableController).
3. I drag a Table View to my window.
4. I CTRL+Drag from the Table View to my tableController.h so to create the outlet "tableView"
5. I create these functions in the interface file:
-(int)numberOfRowsInTableView:(NSTableView *)cocoaTV;
-(id)tableView:(NSTableView *)cocoaTV:objectValueForTableCollumn:(NSTableColumn *)tableCollumn row:(int)row;
6. I implement the functions like this:
-(int)numberOfRowsInTableView:(NSTableView *)cocoaTV{
return 5;
}
-(id)tableView:(NSTableView *)cocoaTV:objectValueForTableCollumn:(NSTableColumn *)tableCollumn row:(int)row{
NSArray *tvArray = [[NSArray alloc]initWithObjects:#"1",#"2",#"3",#"4",#"5", nil];
NSString *v = [tvArray objectAtIndex:row];
return v;
}
Then I CTRL+Drag from the Object in the Interface Builder to the Table View to set the dataSource and to set it as delegate.
When I build and Run the App it shows that it has created the 5 Rows but in every cell in every column it says "Table View Cell".
Any help would be appreciated....
-(id)tableView:(NSTableView *)cocoaTV:objectValueForTableCollumn:(NSTableColumn *)tableCollumn row:(int)row is wrong.. i'm not sure how it compiles, to be honest (unless there was an error copy/pasting it). the method should look like:
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSArray *tvArray = [[NSArray alloc]initWithObjects:#"1",#"2",#"3",#"4",#"5", nil];
NSString *v = [tvArray objectAtIndex:row];
return v;
}
Objective C - iPhone Application
I have 2 programatically instantiated UINavigationControllers
In controller 1 I push this View
-(IBAction) showStartDateCalendar
{
ModalSetDateController *setStartDate = [[ModalSetDateController alloc] init];
[self.navigationController pushViewController: setStartDate animated:YES];
[setStartDate release];
};
To push it back I was planning on doing something like this:
-(IBAction) PopMyViewControllerBack;
{
[self.navigationController popViewControllerAnimated: YES];
}
But how would I be able to get the value of the datepicker on ModalSetDateController
Is this the best way to handle adding a UIPickerview. I would prefer to not even have a UINavigationController do it.
Thanks for your help!
Use:
-(IBAction)popMyViewControllerBack:(id)sender
and sender will be an instance of ModalSetDateController.