Calling a view in macOS from outside a view controller - objective-c

My app uses in app purchases, and if the user is found to not have the premium version, then the class handling the function they tried to use will call on a method from MKStoryKit, a class that makes dealing with StoryKit easier. MKStoryKit is not a view or a view controller, it's simply for utility. The view I want to call is a custom view which provides them with the features of the premium version and gives the user a few options, while looking a little bit prettier than just a system alert. On my iOS app, after declaring the view from the storyboard (this view has no segues going to it) using:
NSString *storyboardName = #"Main";
NSString *viewID = #"TermsOfUseQuery";
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
UIViewController *Terms = [storyBoard instantiateViewControllerWithIdentifier:viewID];
I call the following line which simply presents the previously declared UIViewController "Terms" on top of whatever is currently happening:
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:Terms animated:YES completion:nil];
I was wondering if there was a similar method for macOS, i've been unable to find it. I am aware that, generally, anything "UI" on iOS becomes "NS" and I've already accounted for that, like declaring NSViewController.
As an alternative, I've managed to call the view from inside the main view controller using:
let board:NSStoryboard = NSStoryboard(name: "Main", bundle: nil)
let viewtest:NSViewController = board.instantiateController(withIdentifier: "termsOfUseVC") as! NSViewController
self.presentViewControllerAsModalWindow(viewtest)
This has the effect that I am looking for, however it has to be achieved from MKStoryKit (which is an Objective-C class). I was wondering if anyone had a solution for this, either to just display the view as a modal window at any time (preferred, effectively what that single line does) or to call it as a modal window on top of the current "top" view? Thanks to everyone!

I found a way to do it, posting this for future reference in case anyone else needs this in the future, or if anyone else tries using Google for this:
[[NSApplication sharedApplication].keyWindow.windowController.contentViewController presentViewControllerAsModalWindow:Terms];
This will present a view from the storyboard from the most recent Window of your application, though that "from" part is not too relevant as this will show the View as its own window. If you chose to use:
[[NSApplication sharedApplication].keyWindow.windowController.contentViewController presentViewControllerAsSheet:Terms];
Then this would present it as a slidedown from the last window, this is where the window's identity, if you have multiple, matters. The "Terms" present in both lines is a NSViewController that I've shown how to declare in my original answer (Except this is NS instead of UI, macOS vs iOS difference). The actual declaration is below for the sake of clarity:
NSString *storyboardName = #"Main";
NSStoryboard *storyBoard = [NSStoryboard storyboardWithName:storyboardName bundle:nil];
NSViewController *Terms = [storyBoard instantiateControllerWithIdentifier:#"termsOfUseVC"];
Hope this helps someone.

Related

Can't modal to next viewcontroller

I feel like I am just missing something simple here. I am trying to modal to my next view controller. I imported the next view controller in my first .m file first. After I did that I wrote this code
CRHViewController *nextViewController = [[CRHViewController alloc]init];
[self presentModalViewController:nextViewController animated:NO];
Also, I am working with storyboard and not nibs.
What happens when I run this is as soon as it goes to modal to the next viewcontroller it just goes black.
Am I missing something simple? Does anyone have an suggestions to fix this problem?
Probably you're not initialising it correctly. For testing purposes I would try to display CRHViewController as first root viewController from AppDelegate and find if it is initialising at all. Then check if it gets to its methods:
initWithNibName
loadView
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
In this order. Its 90% certain that one of them fails. Check if it gets to every method you have implemented correctly in this order.
If your CRHViewController is in your storyboard, then you should be instantiating it with:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"yourStoryboardName" bundle:nil];
[storyboard instantiateViewControllerWithIdentifier:#"myIdentifier"];
You should give your view controller an identifier in IB to pass in as the identifier parameter in the above method.

presentViewController - view solid black, viewDid___ methods not called

...
SecondViewController *svc = [SecondViewController new];
[self presentViewController:svc animated:YES completion:NULL];
}
This code is exactly the same as what I used in another app, but here I'm using presentViewController rather than presentModalViewController
(completion:NULL makes them effectively identical. Same result, at least.)
Both attempts at creating a modal view are structured the same way. Those lines in the main view, a view controller in the Storyboard, and matching .h and .m files. The only difference is that here I want a programmatic trigger, so it's impossible to drag a segue and be done with it.
I have an object set to recognize a gesture and call the transition method. That's probably what's causing the problem (part of it, at least), but it is necessary.
Using a UIButton would be cheating. No deadline, no shortcuts.
EDIT: NSLog output shows something odd.
2012-04-05 10:41:12.047 MyApp[5962:707] <SecondViewController: 0x1d8c130>
2012-04-05 10:41:12.479 MyApp[5962:707] <SecondViewController: 0x1d8e360>
So I'm doing something stupid again that happens to have a very simple fix, right?
Edit again: presentViewController… was being called more than once. Fixed it. Still black, though.
Back to performSegueWithIdentifier:sender: instead of the much easier presentViewController:animated:completion:
Terminating app due to uncaught exception 'NSInvalidArgumentException", reason: 'Receiver … has no segue with identifier …'
I told it to perform a segue, but there isn't one in the Storyboard (I can't add one, there is no Storyboard Segues section under 'Connections inspector' for the object I'm attempting to use), so it crashes. This is normal behavior.
What I want is to have a modal view without needing to create a segue. I've done it, so I know it's possible.
All I need is a bit of help getting it to work.
.
performSegueWithIdentifier:#"Identifier" sender:nil NSSInvalidArgumentException
presentViewController:viewController animated:YES completion:NULL Empty View
Got it.
Replace the lines in the question with this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard"
bundle:nil];
SecondViewController *viewController =
[storyboard instantiateViewControllerWithIdentifier:#"SecondView"];
[self presentViewController:svc animated:YES completion:NULL];
Credit for this solution goes to IturPablo's self-answered question:
TabBarController, overwriting shouldSelectViewController to do a segue
Are you looking for performSegueWithIdentifier:sender:? The docs seem to match your description:
"you can call this method to trigger a segue programmatically, perhaps
in response to some action that cannot be specified in the storyboard
resource file"

Obj-C, Navigation Controller with Tab Controller am I using them incorrectly?

When I first setup my app I had some issues getting a single navigation controller to work.
I have several screens behind each tab item. I think the problem I was getting was that view controllers would show within the wrong tabs, when switching between them. I'm not bothered about keeping the last view controller used open within each tab, in fact I hide the tab bar to stop this anway now.
So at the moment I have navigation controller files for each of my tabs. I have them assigned in IB, in the mainWindow.
And I use them like this...
CategorySelTableViewController *nextController =
[[[CategorySelTableViewController alloc] initWithNibName:
#"CategorySelTableView" bundle:nil] autorelease];
nextController.hidesBottomBarWhenPushed = YES;
MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication]
delegate];
[delegate.billsndepsNavController pushViewController:nextController animated:YES];
However, I have some leaks.
I can't release my delegate, it causes an error.
My colleuge suggests that I should just be using self.navigationcontroller.
But this is a big change for me, I'd like to know definetively if I'm doing this wrong before I make the changes ?
When a view controller is pushed into the stack, it has two ways to access the navigation controller:
Using self.navigationController.
Accessing the navigation controller ivar in the delegate:
[UIApplication sharedApplication].delegate.navigationController
Both are equivalent but the first one is shorter, so it tends to be used more. There is no benefit from switching from one to the other. The only reason to do the extra typing is when you are not in a pushed view controller, eg: a view controller used in an independent GUI component, or an object that is not a view controller.
The delegate shouldn't be released because it exists during the whole life of the application.

A question about [UIViewController alloc]

I am going through some sample code from Apple. The following 5 statements are from MoveMeAppDelegate.m belonging to the "MoveMe" sample project:
UIViewController *aViewController = [[UIViewController alloc] initWithNibName:#"MoveMeView" bundle:[NSBundle mainBundle]];
self.viewController = aViewController;
[aViewController release];
UIView *controllersView = [viewController view];
[window addSubview:controllersView];
From documentation, #"MoveMeView" defines the name of a xib file. But it seems that a xib file can have more than one view object. And in the last statement above, the controllersView is added to a window object. My question is if the window has more than one view, then how does the compiler know which view is the above codes referring to ?
If you're asking how your app knows which view is the controller's view: in your .xib file, you "attach" one of the views to the controller. This relationship is called an outlet, and in code is signified by the IBOutlet tag on an instance variable or on a property.
For more information, check out Apple's Resource Programming Guide--specifically, the section entitled "Nib Files."
If you're asking how the window knows which views belong to it: a window on iOS is also a view, and can have a (nearly) unlimited number of subviews.
If you're asking something else... I don't know what you're asking, so please clarify.

Create UINavigationController programmatically

Just because I am unable to find a secure way (in a sense that it can be rejected by Apple guys) to customize UITabbar, in particular UITabBarItem I am trying some workaround.
I have a main view on which I recreate a kind of UITabBar, a normal view with two buttons inside. This is (roughly) the current hierarchy:
-MainView
--placeholder(UIView)
--fakeTab (UIView)
What I want to do is, after tapping a button in fakeTab, build a UINavigationController and add it to "placeholder" view so that the fakeTab remain on top and the whole navigation happens on the placeholder level.
I already tried with this piece of code in the method that it's intercepting tap button, and it works, I can see the ipvc.view added to placeholder.
IPPlantsViewController *ipvc = [[IPPlantsViewController alloc] initWithNibName:#"IPPlantsView" bundle:[NSBundle mainBundle]];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:ipvc];
UIView *placeholder = [self.view viewWithTag:200];
[placeholder addSubview:ipvc.view];
But later when I call from inside ipvc, then nothing happens:
IPAttributes *ipas = [[IPFactory findPlantByIndex:indexPath.row] attrs];
[self.navigationController pushViewController:ipa animated:YES];
I find the solution myself. What I was doing wrong is to attach the ipvc controller view to placeholder. Instead of doing this:
[placeholder addSubview:nav.view];
and everything works as expected, with my fake tabbar fully customized :-)
But, as a side note, the viewWillAppear seems to be never called.
It would be interesting to know why. I partially solved by making IPPlantsViewController the delegate of the UINavigationController.