How can I handle custom events in iOS development? - iphone

We're working on a project using some custom views. We have the following hierarchy:
UIViewController -> UIScrollView (custom subclass) -> UIView (custom subclass)
We are presenting a grid of buttons that are dynamically generated. When a user taps one of the UIViews that belong to the custom scroll view we fire a method that looks like this:
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer {
[[self superview] itemSelected:self];
}
The super view in this case is our custom subclass of UIScrollView. From here we fire another method:
- (void) itemSelected: (id)selectedItem {
itemView *item = selectedItem;
[[self superview] initSliderViewForItemNamed:item.name];
item = nil;
}
Now here is where things break. We want to fire another method in UIViewController to load a new view at the top of our view hierarchy. So in UIViewController we have this method to test for success:
- (void) initSliderViewForItemNamed:(NSString *)selectedItemName {
NSLog(#"Selected item was found! %#",selectedItemName);
}
But we never reach this point and the app crashes. It's because we can't reference the UIViewController here. Instead we're referencing the view property of the UIViewController. So our actual object hierarchy is:
UIViewController.view -> UIScrollView (custom subclass) -> UIView (custom subclass)
This leads me to two questions.
How do we reference the UIViewController from our subview that belongs to the controller's view property?
Is this method convoluted. Is there a better way to do this? Should we be assigning the the UIViewController as a delegate of our custom subclass of UIScrollView?

Jim you should setup a delegate on your custom uiview subclass and let you view controller be it's delegate and conform to the protocol you just created and you will be fine (ie: what a tableview is doing)

Yeh either using a delegate or you implement:-
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
or
(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
depending on how you want to handle the touch.

What I ended up doing to solve this was assign the View Controller as the delegate of the scroll view. It looks like this:
itemScrollView = [[ItemScrollView alloc] initWithFrame:CGRectMake(...)];
[itemScrollView setDelegate:self];
[self.view addSubview: itemScrollView];
Then in my ItemScrollView Implementation I could refer to the ViewController with:
[[self delegate] initSliderViewForItemNamed:selectedItem.name];
Big thanks to everyone who replied. So setting the View Controller as the delegate was the correct answer, however, no one went into detail well enough as to how to do this. So I've covered that here in this response. Additional information can also be found on this question involving delegates:
How do I create delegates in Objective-C?

The idiomatic way to do what you want would be to send the message up the responder chain. UIViewController takes part in the responder chain, so it will receive the message.

Related

UINavigationController transition animations triggered too fast

I'm using a custom-made container view controller in my dictionary app. Basically, the container view controller contains a custom navigation bar on top (NOT a UINavigationBar--just a UIView with back and forward UIButtons, a UISearchBar, and a bookmark UIButton at the right), and a tab bar controller at the bottom.
My problem is this: I use the back and forward buttons to push and pop view controllers in one of the tabs (a UINavigationController) so the user can navigate through the dictionary browsing history. However, if I press the back or forward buttons too fast, I get this message in the log pane and some of the screens don't appear at all:
Unbalanced calls to begin/end appearance transitions for
<DefinitionViewController: 0x8e5d230>.
Looking around StackOverflow, I understood that this is because clicking on the back or forward buttons too fast calls the push/pop methods of the UINavigatonController in the active tab, but it does not let the animation finish. https://stackoverflow.com/a/17440074/855680
Pushing or popping view controllers without the animations solves the problem, but I do want to keep the animations. How can I approach this problem? I looked at the UINavigationController class reference to see if there are any delegate methods or properties that indicate that it's in the middle of an animation, but there doesn't seem to be any.
Fixed it myself. The solution was to create a property in my container view controller which indicates whether the UINavigationController transition animations are still happening:
#property (nonatomic, getter = isStillAnimatingTransition) BOOL stillAnimatingTransition;
Now, for all the UIViewController classes that I push into the UINavigationController, I set this flag to YES or NO in each of the view controllers' viewWillDisappear and viewDidAppear methods, like this:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.containerViewController.stillAnimatingTransition = NO;
}
- (void)viewWillDisappear:(BOOL)animated
{
self.containerViewController.stillAnimatingTransition = YES;
[super viewWillDisappear:animated];
}
And my container view controller only ever allows the execution of the back and forward buttons if the animation flag is set to NO, like this:
- (void)backButtonClicked
{
if (!self.isStillAnimatingTransition) {
// Do whatever.
}
}
- (void)forwardButtonClicked
{
if (!self.isStillAnimatingTransition) {
// Do whatever.
}
}
Maybe you can take advantage of the UINavigationControllerDelegate class and handle the events there.
In your main class that holds the navigation controller, set the delegate to yourself and handle the interactions there.
i.e. in the .h file:
#interface yourClass : UIViewController <UINavigationControllerDelegate> {
UINavigationController *content;
}
and then in the .m file:
content = [[UINavigationController alloc] initWithRootViewController:yourRootViewController];
content.delegate = self;
After that you can listen to the transition events via the following functions, and set your animation flags accordingly.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
stillAnimatingTransition = NO;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
stillAnimatingTransition = YES;
}
You can find more references about the delegate protocol from apple
https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationControllerDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UINavigationControllerDelegate/navigationController:willShowViewController:animated:

Best way to call method that exists in multiple classes

I have a main UIViewController where most of the users' interaction happens. In the main ViewController there are three subviews. The user can load separate ViewControllers into the UIView subviews.
Each of the subviews that are loaded deal with data entry. In turn, firstResponders are called. I would like to be able to dismiss the firstRespnders through the main ViewController, maybe with a 'Done' button.
I was thinking I could add a method in each of the separate subviews with one name ex;
-(void) methodToResignResponders {}
Then, in the main ViewController call this method to the view that is currently open to the user. In turn resigning the responders that are active in the subview.
Further Information:
This is how I set up each view as a subview of the main ViewController:
UIViewController *calcVC;
//set up the view to be added depending on the name of the view that was passed
if ([viewName isEqualToString:#"Tax"]) {
calcVC= [[TAXViewController alloc]initWithNibName:#"TAXViewController" bundle:nil];
}else if ([viewName isEqualToString:#"Rent"]){
calcVC= [[RENTViewController alloc]initWithNibName:#"RENTViewController" bundle:nil];
}else //continues with more views...
//Then add it to the subview
[firstView addSubview:calcVC.view];
Not sure if I've got the gist of this, mostly because it sounds like you've already solved it yourself. :)
But, from what I can see the ViewController you are talking about is always an UIViewController instance named calcVC. If it is always this viewController's view you are referring to you can simply call [calcVC.view resignFirstResponder];
You can make a basic protocol that all of your sub-view controllers implement that has does everything you need (resign first responder and anything else).
Not sure if this answers your question but you can loop through all the subviews and call it if it exists as follows:
for (UIView *subview in [self.view subviews]) {
if ([subview respondsToSelector:#selector(resignFirstResponder)]) {
[subview resignFirstResponder];
}
}

How to put an UITableViewController in UIView to make touchesBegan to work in iPhone?

I create & present modally an UITableViewController(say Table2) class from another UITableViewController class(say Table1) like this..
-(void)createTable2 {
Table2Controller *table2Controller = [ [Table2Controller alloc] initWithStyle:UITableViewStyleGrouped];
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:table2Controller];
[self.navigationController presentModalViewController:nav animated:YES];
[nav release];
[table2Controller release];
}
So the Table2 will be shown. I want to use touchesBegan method to resign keyboard in Table2 because i have some UITextField as cells. I included UITextFieldDelegate protocol in .h file of Table2.
But i knew that these touchesBegan method will work only with UIView & not with UIViewController(Am i right?). But i dont know where & how(I tried in the createTable2 function itself. It does not work) to add an UIView and then add Table2 in that UIView to do things happen... Any advice....
Your table view controller has a table view property. You can subclass the table view and then override methods such as -touchesBegan:withEvent:. Instantiate your custom table view and set this instance to the view property.
UIViewController controls the UIView and other UI elements presented on the screen. All those elements can respond to touches thanks to UIResponder class that they are subclassing.
Use Table2Controller to override touchesBegan method to control the touch events that happen on any UI element within this viewController.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[inputTV resignFirstResponder];
[super touchesBegan:touches withEvent:event];
}
Note always call super method declaration so that your touch can travel up the respond chain.

Calling method in parent UIViewController, after adding by addSubview

I have a UIViewController that is creating another view controller, and adding its view as a subview:
In the parent UIViewController:
SlateMoreView* subView = [[SlateMoreView alloc] initWithNibName:#"SlateMoreView" bundle:nil];
[self.view addSubview:subView.view];
I then need to call a method from the subview, in the parent view.
I have seen how to do this when I am adding the sub UIViewController using [self.navigationController pushViewController: subView animated: YES], because I can find the parent using this kind of code:
In the sub view UIViewController:
NSArray* viewControllerArray = [self.navigationController viewControllers]
int parentViewControllerIndex = [viewControllerArray count] - 2;
SlateView* slateView = [viewControllerArray objectAtIndex:parentViewControllerIndex];
...and then I can send messages to it. But since I added the sub view manually by using addSubView, I can't do this.
Can anyone think of how I can talk to my parent UIViewController?
Thanks!
UIViews have a superview property which seems to be what you are looking for.
In addition you probably don't want to nest UIViewController's view like that unless you are very deliberately building a custom contain view controller. See http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/
You might want to consider if your problem can be solved by using NSNotifications. You could post a notification from your subview when an event happens that interested listeners (your superview) need to know about . When the superview receives the notification, it can run whatever code you wish. All the while the subview never needs to know about the superview.
This is one way to make your classes less dependant on each other.
You could also use delegation as another option.
When you add your view as a subview to a view hierarchy, you put it in the responder chain. You can go up the responder chain to reach the view controller as a UIView controlled by a UIViewController has the UIViewController as its nextResponder.
id object = theSubview;
do {
object = [object nextResponder];
} while ( ![object isMemberOfClass:[YourViewController class]] );
// object has the view controller you need.

touchesBegan and other touch events not getting detected in UINavigationController

In short, I want to detect a touch on the navigation controller titlebar, but having trouble actually catching any touches at all!
Everything is done without IB, if that makes a difference.
My app delegate's .m file contains:
MyViewController *viewController = [[MyViewController alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[window addSubview:navigationController.view];
There are a few other subviews added to this window in a way that overlays navigationController leaving only the navigation bar visible.
MyViewController is a subclass of UIViewController and its .m file contains:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
NSLog(#"ended\n");
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
NSLog(#"began\n");
}
}
I also tried putting these functions directly into app delegate's .m file, but the console remains blank.
What am I doing wrong?
Well, for lack of a better idea, I added another subview to my app, clear in color, placed programmatically over the navigation bar title and used a custom class for that view with relevant touch methods overridden. Works fine, but the I still wish there was a more elegant solution.
The view controller is inserted into the responder chain between its managed view and the superview:
Because view controllers are tightly bound to the views they manage, they are also part of the responder chain used to handle events. View controllers are themselves descendants of the UIResponder class and are inserted into the responder chain between the managed view and its superview. Thus, if the view managed by a view controller does not handle an event, it passes the event to its view controller, which then has the option of handling the event or forwarding it to the view’s superview.
(the UIViewController documentation)
Is it possible, that the managed view of your controller is eating all the events? What kind of view is it?
Had trouble with this, as my custom view was deeper in the view hierarchy. Instead, I climbed the responder chain until it finds a UIViewController;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Pass to top of chain
UIResponder *responder = self;
while (responder.nextResponder != nil){
responder = responder.nextResponder;
if ([responder isKindOfClass:[UIViewController class]]) {
// Got ViewController
break;
}
}
[responder touchesBegan:touches withEvent:event];
}
try adding the method userInteractionEnabled = YES to your UIImageView
These methods should be put into the UIView subclass not the UIViewControllers...The UIView will receive the touches call backs, then you can make a protocol on the UIView and implement it on the UIViewController so the UIViewController will receive some call back when the touch events occur...Here is a link that talks about protocols and how to define them and implement them Protocols Ref