Determine if instance of UIViewController subclass exists - objective-c

How can I examine a list of all active objects that inherit UIViewController?
I'd like to know if an instance of MyViewController exists. Ideally I can get this information in a callback in UIApplicationDelegate (for example application:didReceiveRemoteNotification:).
I've tried logging something like navigationController.viewControllers w/ no luck. I've also tried topViewController and modalViewController properties on navigationController.

If you know for a fact that your rootViewController is a UINavigationController, you can iterate through the array of viewcontrollers and test it for a class type
BOOL success = NO;
NSArray *viewControllersArray = self.navigationController.viewControllers;
for (id vc in viewControllersArray)
{
if ([vc isKindOfClass:[MyViewController class]])
success = YES; // Found it!
}

Related

How can I access a DetailViewController method from SourceViewController; both inside a NSSplitViewController (Objective-C)

I'm new to Objective-C and feel it's important to learn. I gave myself the following challenge:
I have a NSSplitViewController setup with two classes for each view; (i) SourceViewController: NSViewController and (ii) DetailViewController: NSViewController.
By selecting a NSTableView row of the SourceViewController I want to change the image in the ImageView within the SourceViewController
I have an instance class method in DetailViewController that I'm trying to access from SourceViewController:
-(void) imageSelected: (NSString*) name{
self.imageView.image = [NSImage imageNamed:name];
}
To gain access, I have created a pointer to the DetailViewController class instance via the NSSplitViewController superclass. So I go up the tree and down again to the DetailViewController:
//gets the row number that was selected
-(void) tableViewSelectionDidChange:(NSNotification *)notification{
NSLog(#"%ld",[[notification object] selectedRow]);
NSInteger row = [[notification object] selectedRow];
//get access to the parent view controller
//NSSplitViewController *splitCV = self.parentViewController;
NSSplitViewController *splitCV = ((NSSplitViewController *)self.parentViewController);
NSViewController *imageVC = splitCV.childViewControllers[1];
NSLog(#"%#",imageVC.className); //detail view controller
//***** HERE'S THE PROBLEM******
imageVC.imageSelected(self.pictures[row]); //<- imageSelect isn't recognised
}
However, this doesn't work as autocomplete doesn't recognise the function pointed to by imageVC. printing the imageVC.className output, shows that the name is 'DetailViewController'.
What am I doing wrong and how can I fix this?
Thanks in advance
Can you try changing this:
NSViewController *imageVC = splitCV.childViewControllers[1];
into this:
DetailViewController *imageVC = splitCV.childViewControllers[1];
and see if imageVC.imageSelected(self.pictures[row]) now autocompletes correctly ?

Simple Cocoa App with ViewController

I am trying to create a custom NSViewController and just log something out in viewDidLoad. In iOS, this is very trivial and works fine. However, when I setup a contentViewController on NSWindow (which i assume is similar to RootViewController in iOS?) it attempt to load it from a nib.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.ABViewController = [[ABViewController alloc] init];
self.window.contentViewController = self.ABViewController;
}
2016-06-28 09:15:42.186 TestApp[32103:33742217] -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: ABViewController in bundle (null).
What assumptions am I missing about how Cocoa is different from iOS that prevent me from simply setting up a viewController?
NSViewController doesn't have the same behavior as UIViewController, in that it won't automatically know to look for a nib file with the same name as itself. In other words, it won't automatically know to look for the ABViewController.nib file.
The simplest way to fix this is just override the nibName method in ABViewController:
#implementation ABViewController
- (NSString *)nibName {
return NSStringFromClass([self class]);
}
#end
Note that using NSStringFromClass() is usually better than trying to hard code the string, as this way will survive refactoring.
You can then call [[ABViewController alloc] init]; like before and NSViewController's default init method will get the nib name from your overridden nibName method.
It looks like your program wan't find the file where the view's are defined. You need to do something like this for storyboards:
UIStoryboard *sboard = [UIStoryboard storyboardWithName:#"StoryboardFileName"
bundle:NSBundle.mainBundle()];
SecondViewController *vc1 = [sboard instantiateInitialViewController];

Passing data back and forth using AppDelegate

To start I am building an app to learn the basics of Objective-C. If there is anything unclear please let me know and I will edit my question.
The app is supposed to have the next functionality.
Open the camera preview when the app is executed. On the top there is a button to go to a TemplateController where the user can select an array of frames to select from a UICollectionView. User selects the Template and returns to the Camera Preview. User takes a picture and the picture with the frame selected is shown in the PreviewController. If the user doesn't like the frame and wants to switch it for another one. PreviewController has button on top to go to the TemplateController, select the frame and go back again to the PreviewController with the new frame.
I do not want to create an object for the frame everytime. I want the AppDelegate to hold that object. To keep it alive per say?(sorry, English is not my mother tongue).
I was thinking to use NSUserDefaults BUT I really want to do it using the AppDelegate. So at this point NSUserDefaults is not an option.
Now, I am using storyboards with a navigation controller. A screenshot is available here
Right now when I pass from the TemplateController to my PreviewController my code looks like this:
Reaching TemplateController from MainController or PreviewController
- (IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
Passing the data from TemplateController to its controller's destiny (Either MainController or PreviewController)
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
_selectedLabelStr = [self.frameImages[indexPath.section] objectAtIndex:indexPath.row];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
[self dismissViewControllerAnimated:YES completion:^{
if ([self.frameDelegate respondsToSelector:#selector(templateControllerLoadFrame:)])
{
[self.frameDelegate performSelector:#selector(templateControllerLoadFrame:) withObject:self];
}
}];
}
This loads the selected frame in PreviewController
- (void)templateControllerLoadFrame:(TemplateController *)sender
{
UIImage *tmp = [UIImage imageNamed:sender.selectedLabelStr];
_frameImageView.image = tmp;
}
My problem is, I don't have very clear what changes I have to do on the AppDelegate(it is untouched right now). What would be the best approach to accomplish this?
Main issue is when Tamplate is chosen before taking the still image. If I select the frame after taking the picture then it displays.
I am not certain that I understand your question. Stuffing an object into the app delegate solution may not be the best way forward. In fact I believe you ought to look at the delegation pattern that is used by Apple to communicate between view controllers. Please note that you appear to be doing half of the delegate pattern already. For example you make your PreviewController a frameDelegate of the TemplateController.
So I would think you'd have something like the following to transfer information from TemplateController back to the PreviewController. Note that I've included prepare for segue as that is a common pattern to push a data object forward (it will be called if you connect a segue from the PreviewController to the TemplateController and in your action method call performSegueWithIdentifier:#"SegueTitle"). Use of the "templateControllerDidFinish" delegation method is a common pattern used to push information back from TemplateController when it closes.
TemplateController.h
#class TemplateController;
#protocol TemplateControllerDelegate <NSObject>
-(void) templateControllerDidFinish :(TemplateController*)controller;
#end
#interface TemplateController : UIViewController
#property (nonatomic, weak) id <TemplateControllerDelegate>delegate;
...
#end
TemplateController.m
//! The internals for this method can also be called from wherever in your code you need to dismiss the TemplateController by copying the internal
-(IBAction)doneButtonAction:(id)sender
{
__weak TemplateController*weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[self.delegate templateControllerDidFinish:weakSelf];
}];
}
PreviewController.h
#import "TemplateController.h"
#interface PreviewController<TemplateControllerDelegate>
...
#end
PreviewController.m
#implementation
...
-(void) templateControllerDidFinish :(TemplateController*)controller
{
self.dataProperty = controller.someImportantData;
...
}
...
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ( [[segue identifier]isEqualToString:#""] )
{
TemplateController *tc = [segue destinationViewController];
tc.delegate = self;
tc.data = [someDataObjectFromPreviewController];
}
}
To fix this situation a bit more:
Add a segue from the PreviewController to the TemplateController
(Ctrl-drag from Preview view controller to the Template Controller
in the document outline mode)
Name the segue identifier in the identity inspector
Change your code that presents the view controller from:
(IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
to
- (IBAction)showFrameSelector:(id)sender
{
[self performSegueWithIdentifier:#"SegueTitle"];
}
Add your data object to the target view controller as noted in prepareForSegue and you will be in good shape. Then use the delegate method to catch any data returned from your template (just add the data as properties to the controller and you should be golden)
You can see a better example of this delegation in a utility project template from Xcode (I just keyed this in..) I hope this information helps. You can get more information at these resources and also by searching Google and SO for iOS delegation :
Concepts in Objective C (Delegates and Data Sources)
Cocoa Core Competencies

firstResponder in NSViewController

I've got two classes. ManagingViewController, a subclass of NSViewController, and ViewController, a subclass auf ManagingViewController. In Viewcontroller I've got a NSTextField which I want to become the firstResponder, but I didn't manage that.
So it is nearly the same like the Chapter 29 in Hillegass' book Cocoa Programming for Mac OS X (Download of the book's examples) except of an NSTextField which is set to firstResponder.
Can anybody point me to the correct way?
You need to set the text field as the first responder by using -[NSWindow makeFirstResponder:].
Since this is an NSWindow method, it only makes sense after you’ve added the corresponding view to the window, i.e., after you’ve added the view as a subview inside the window view hierarchy. In the book’s example, this happens when you set the view as the content view of the box inside the window. For example:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
if ([vc class] == [ViewController class]) {
[w makeFirstResponder:[(ViewController *)vc myTextField]];
}
}
This assumes ViewController exposes a getter method called -myTextField.
You can make this more generic by having your view controllers expose a method that returns the object that the view controller recommends as the first responder. Something like:
#interface ManagingViewController : NSViewController
…
- (NSResponder *)recommendedFirstResponder;
#end
#implementation ManagingViewController
…
- (NSResponder *)recommendedFirstResponder { return nil; }
#end
And, in your concrete subclasses of ManagingViewController, have -recommendedFirstResponder return the object that should be the window’s first responder:
#implementation ViewController
…
- (NSResponder *)recommendedFirstResponder { return myTextField; }
#end
Having done that, you can change your -displayViewController: to something like:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
NSResponder *recommendedResponder = [vc recommendedFirstResponder];
if (recommendedResponder) [w makeFirstResponder:recommendedResponder];
}
Have you tried [[myTextField window] makeFirstResponder:myTextField]; ?
simple. Goto you xib file in interface builder. right click the first responder field. it will show the connection , remove the connection and connect it to the desired responder. let me know if this works

Show Next and previous Detail UIView without going back to parent UITableView

I have a Nav controller that starts at a table view. Each row pushes to a detail UIView. I would like to have a next and previous button on the Detail UIView that would show next and previous views without going back to parent UITableView. All details are saved in an array. how can i access that array and its current index in DetailViewController.m .Thanx in advance.
Another clean (and elegant) way is to build some sort of connection between the table view datasource, which is normally the UIViewController that contains the table, and the detail view: this can be done using the Delegate pattern, typical of Cocoa framework.
In such case you can define a DetailViewDataDelegate protocol with two methods only:
-(id)nextTableObjectFrom:(id)referenceObject;
-(id)prevTableObject;(id)referenceObject;
where referenceObject is the calling object, that is the object detailed in the DetailView.
So DetailView will be defined in this way:
#interface DetailViewController:UIViewController {
}
#property (nonatomic,assign) id currentDetailedObject;
#property (nonatomic,assign) id dataDelegate;
and of course when you call the controller, typical from the tableView:didSelectElementWithIndexPath:animated: method, you will do something like this:
DetailViewController *dv = [[[DetailViewController alloc] initWithNibName:nil bundle:nil] autorelease];
dv.dataDelegate=self;
dv.currentDetailedObject=[mySourceArray objectAtIndexPath:indexPath.row];
[self.navigationController pushViewController:dv animated:YES];
Finally in the DetailViewController when you need the next (or prev) element from the table you will simply call:
-(IBAction)nextButtonPressed {
self.currentDetailedObject = [self.dataDelegate nextTableObjectFrom:self.currentDetailedObject];
}
Of course the implementation details may change, and in particular the delegate methods, to be implemented in the table's UIViewController, will depend on the data structure.
The advantage of this approach, which can be at first sight complicated, is quite elegant and avoids to pass objects along controllers, which is often a source of memory issues. Besides with the delegate approach you can implement any complicated feature (e.g.: you can even manipulate table view objects directly from the DetailViewController, at your own risk of course!)
The cleanest way is to create a custom initializer for DetailViewController:
#interface DetailViewController : UIViewController
{
NSArray* allObjects;
NSUInteger displayedObjectIndex;
}
- (id) initWithObjectAtIndex: (NSUInteger) index inArray: (NSArray *) objects;
#end
#implementation DetailViewController
- (id) initWithObjectAtIndex: (NSUInteger) index inArray: (NSArray *) objects
{
self = [super initWithNibName:nil bundle:nil];
if ( self ) {
allObjects = [objects copy];
displayedObjectIndex = index;
}
return self;
}
#end
That way, a DetailViewController always knows both what object it is displaying details for and the previous/next objects, if any.