UIGestureRecognizer between Swift and Objective-C - objective-c

I have a Swift application view adding some component written in ObjC.
I'm adding the ObjC UIViewController class to Swift view in this way, which works anyway (I don't want the view to be a popup one):
view.addSubview(myObjcViewController.view)
Although, the ObjC ViewController's view populated properly without any problem, different gesture recogniser written in that viewController is not working anymore:
UISwipeGestureRecognizer *upSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(upButtonTapped)];
upSwipe.numberOfTouchesRequired = 1;
upSwipe.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:upSwipe];
I tried to change the gestureRecognizer addition in following ways, but none worked:
[self.parentViewController.view addGestureRecognizer:upSwipe];
// or
[[[[UIApplication sharedApplication] delegate] window] addGestureRecognizer:upSwipe];
Please, help.

The problem is that you cannot simply grab a view controller's view and stuff it into the interface like this:
view.addSubview(myObjcViewController.view)
Instead, there is an elaborate dance that you must do in order to ensure that the two view controllers (the one that owns view and the one that owns myObjcViewController.view) both persist in a coherent parent-child relationship.
You are not doing that dance. Therefore the "child" view controller just goes out of existence, and there is nothing containing any code for the gesture recognizer to talk to.

Related

Adding Subviews to NSView show up, but can not be removed

In my application window I have two NSViews. On the left the NSView ("Menu") contains a few buttons. When one of the buttons is clicked it should change the contents of the right NSView ("Content").
For each of the views on the right I have a separate NSViewControllers that get loaded and their views gets added as a subview. When a further button gets pressed on the left the added subviews on the right should be removed and the new view should be loaded as a subview.
To accomplish this I load my Menu in AppDelegate with the following:
MenuVC *menuSubView = [[MenuVC alloc] initWithNibName:#"MenuVC" bundle: nil];
menuSubView.contentView = (NSView*)[self contentView];
[[self menuView] addSubview:[menuSubView view]];
This works fine. As you can see I have a NSView pointer in the Menu VC which points to the contentView so that I can populate it with the subviews.
Now as a method for one of the button presses I do the following:
SomeContentVC *subView = [[SomeContentVC alloc] initWithNibName:#"SomeContentVC" bundle:nil];
[self.contentView addSubview:[subView view]];
This does not work.
If I however add a subview from the awakeFromNib method of the MenuViewController implementation (in the case of default content when the app opens) it works. However when I try to remove that subview using
[[self.contentView setSubviews:[NSArray array]];
I can't. Interesting is also that if I try to count the number of subviews (even after having added one in the awakeFromNib method) it returns 0 subviews for self.contentView. Why? How can I get it to work properly?
Thanks
The fact that messaging self.contentView achieves nothing except, for some things, returning 0 probably means that self.contentView is nil.
Do you perhaps have two instances of MenuVC by accident? Perhaps one instantiated in a NIB and one instantiated in code?
When in doubt, log everything. Log self in various methods. Log menuSubView just after you create it. Log menuSubView.contentView just after you assign it. Etc. Eventually, you'll probably see that you're interacting with different objects than you thought you were.

Replacing Storyboard Segue with pushViewController causes strange behaviour

I can't seem to figure this out for the life of me. I have a custom table view cell, in that cell I have a few buttons configured. Each button connects to other view controllers via a storyboard segue. I've recently removed these segues and put a pushViewController method in place. Transition back and forth across the various views works as expected however the destination view controller is not displaying anything! I have some code below as an example.
Buttons have this method set:
[cell.spotButton1 addTarget:self action:#selector(showSpotDetails:) forControlEvents:UIControlEventTouchUpInside];
// etc...
[cell.spotButton4 addTarget:self action:#selector(showSpotDetails:) forControlEvents:UIControlEventTouchUpInside];
// etc...
showSpotDetails Method contains this code:
- (void)showSpotDetails:(id)sender
{
// determine which button (spot) was selected, then use its tag parameter to determine the spot.
UIButton *selectedButton = (UIButton *)sender;
Spot *spot = (Spot *)[spotsArray_ objectAtIndex:selectedButton.tag];
SpotDetails *spotDetails = [[SpotDetails alloc] init];
[spotDetails setSpotDetailsObject:spot];
[self.navigationController pushViewController:spotDetails animated:YES];
}
The details VC does receive the object data.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"spotDetailsObject %#", spotDetailsObject_.name);
}
The NSLog method below does output the passed object. Also, everything in the details view controller is as it was. Nothing has changed on the details VC. It just does not render anything ever since I removed the segue and added the pushViewController method. Perhaps I am missing something on the pushViewController method? I never really do things this way, I try to always use segues...
Any suggestions?
Welcome to the real world. Previously, the storyboard was a crutch; you were hiding from yourself the true facts about how view controllers work. Now you are trying to throw away that crutch. Good! But now you must learn to walk. :) The key here is this line:
SpotDetails *spotDetails = [[SpotDetails alloc] init];
SpotDetails is a UIViewController subclass. You are not doing anything here that would cause this UIViewController to have a view. Thus you are ending up a with blank generic view! If you want a UIViewController to have a view, you need to give it a view somehow. For example, you could draw the view in a nib called SpotDetails.xib where the File's Owner is an SpotDetails instance. Or you could construct the view's contents in code in your override of viewDidLoad. The details are in the UIViewController documentation, or, even better, read my book which tells you all about how a view controller gets its view:
http://www.apeth.com/iOSBook/ch19.html
The reason this problem didn't arise before is that you drew the view in the same nib as the view controller (i.e. the storyboard file). But when you alloc-init a SpotDetails, that is not the same instance as the one in the storyboard file, so you don't get that view. Thus, one solution could be to load the storyboard and fetch that SpotDetails instance, the one in the storyboard (by calling instantiateViewControllerWithIdentifier:). I explain how to do that here:
http://www.apeth.com/iOSBook/ch19.html#SECsivc

Subclassing in objective c and viewWillAppear message delegates?

I might be confused here and asking the wrong question.
If I use a class like the UISplitViewController inside the appdelete.m, will the only message i will receive is the message the UISplitViewController calls and not any VIEW message? for example:
in my myappdelegate.m
....
UISplitViewController *mySplitViewController = [[UISplitViewController alloc] init];
mySplitViewController.viewControllers = [NSArray arrayWithObjects:leftside,rightside,nil];
...
mySplitViewController.delegate = self;
....
[windows addSubView:mySplitViewController.view];
....
-(void) viewWillAppear:(BOOL) animated {
}
in myappdelegate.h I included UISplitViewControllerDelegate
I expected viewWillAppear to fire but it is not. I assume if I had subclass UISplitViewControler it would have fire. right?
BTW: I am doing this without using IB. Do I need to set the target for the mySplitViewController?
What I want to do is setup the orientation of the splitviewcontroller when it rotates.
the viewWillAppear method and other view related methods will be called on the view or view controller themselves, not on the delegate.
That means that if you make a subclass of UISplitViewController called SplitViewControllerSubClass, the view... methods will be called on the instance of SplitViewControllerSubClass, not on the delegate object.
But considering you are creating the views and displaying them programmatically, you already know exactly when the view will appear (i.e., right before you add it to the window), so I believe you could do whatever setup you want at that point.

iPhone subview design (UIView vs UIViewController)

I'm designing a simple Quiz application. The application needs to display different types of QuizQuestions. Each type of QuizQuestion has a distinct behavior and UI.
The user interface will be something like this:
alt text http://dl.getdropbox.com/u/907284/Picture%201.png
I would like to be able to design each type of QuizQuestion in Interface Builder.
For example, a MultipleChoiceQuizQuestion would look like this:
alt text http://dl.getdropbox.com/u/907284/Picture%202.png
Originally, I planned to make the QuizQuestion class a UIViewController. However, I read in the Apple documentation that UIViewControllers should only be used to display an entire page.
Therefore, I made my QuizController (which manages the entire screen e.g. prev/next buttons) a UIViewController and my QuizQuestion class a subclass of UIView.
However, to load this UIView (created in IB), I must[1] do the following in my constructor:
//MultipleQuizQuestion.m
+(id)createInstance {
UIViewController *useless = [[UIViewController alloc] initWithNibName:#"MultipleQuizQuestion" bundle:nil];
UIView *view = [[useless.view retain] autorelease];
[useless release];
return view; // probably has a memory leak or something
}
This type of access does not seem to be standard or object-oriented. Is this type of code normal/acceptable? Or did I make a poor choice somewhere in my design?
Thankyou,
edit (for clarity): I'd like to have a separate class to control the multipleChoiceView...like a ViewController but apparently that's only for entire windows. Maybe I should make a MultipleChoiceViewManager (not controller!) and set the File's Owner to that instead?
You're on the right track. In your QuizController xib, you can create separate views by dragging them to the xib's main window rather than to the QuizController's main view. Then you can design each view you need according to your question types. When the user taps next or previous, remove the previous view and load the view you need based on your question type using -addSubview on the view controller's main view and keep track of which subview is currently showing. Trying something like this:
[currentView removeFromSuperView];
switch(questionType)
{
case kMultipleChoice:
[[self view] addSubview:multipleChoiceView];
currentView = multipleChoiceView;
break;
case kOpenEnded:
[[self view] addSubview:openEndedView];
currentView = openEndedView;
break;
// etc.
}
Where multipleChoice view and openEndedView are UIView outlets in your QuizController connected to the views you designed in IB. You may need to mess with the position of your view within the parent view before you add it to get it to display in the right place, but you can do this with calls to -setBounds/-setFrame and/or -setCenter on the UIView.
Yeah, IB on iPhone really wants File's Owner to be a UIViewController subclass, which makes what you want to a bit tricky. What you can do is load the nib against an existing UIViewController instead of instantiating one using the nib:
#implementation QuizController
- (void) loadCustomViewFromNib:(NSString *)viewNibName {
(void)[[NSBundle mainBundle] loadNibNamed:viewNibName owner:self options:nil];
}
#end
That will cause the runtime to load the nib, but rather than creating a new view controller to connect the actions and outlets it will use what you pass in as owner. Since we pass self in the view defined in that nib will be attached to whatever IBOutlet you have it assigned to after the call.

How do I make a reusable XIB with it's own UIViewController?

I am trying to create a reusable "picker". It is basically like a keypad of a phone. Since I will be using this a lot in my iPhone app, I have been frustrated with trying to make it appear.
It is in its own XIB file and has its own UIViewController subclass as the FileOwner.
However, when I instantiate this with:
MonthPickerViewController *mpvc
= [[MonthPickerViewController alloc] initWithNibName:#"MonthPicker"
bundle:nil];
Nothing happens on the screen. Yet is does fire the -viewWillAppear methods, etc.
So, what am I doing wrong either in code or in InterfaceBuilder that is preventing my view to appear?
Are you pushing the view controller?
[[self navigationController] pushViewController:mpvc animated:YES];
Or are you adding the view controller's view as a subView of your current view?
First, make sure you've hooked everything up right inside Interface Builder. An easy gotcha is to forget to connect the View object up to the view outlet of your UIViewController subclass.
Then, as Adam says, you need to actually display the view. Assuming you're doing this inside the code of another view controller, you'd need something like the following if you just wanted the new view to appear ontop of your current view:
[self.view addSubview:mpvc.view];
Of if you are using a navigation controller to stack views:-
[[self navigationController] pushViewController:mpvc animated:YES];