I am new to Objective-C and Xcode and I am having difficulty in changing views. I understand you can use the UIScrollView to swipe between image views, but I would like to simply swipe right to change views, and swipe left to return. I have AppDelegate.h and AppDelegate.m which have not been changed. ViewController.h and ViewController.m are given below:
ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#end
ViewController.m:
#import "ViewController.h"
#import "ViewController2.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//sets up gesture recognizer
UISwipeGestureRecognizer *swipeRightGesture=[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGesture:)];
[self.view addGestureRecognizer:swipeRightGesture];
swipeRightGesture.direction=UISwipeGestureRecognizerDirectionRight;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)handleSwipeGesture:(UISwipeGestureRecognizer *)sender
{
NSUInteger touches = sender.numberOfTouches;
if (touches == 2)
{
if (sender.state == UIGestureRecognizerStateEnded)
{
ViewController2 *vc2 = [[ViewController2 alloc] initWithNibName:#"ViewController2" bundle:nil];
[self.navigationController pushViewController:vc2 animated:YES];
}
}
}
#end
I also have ViewController2.h and ViewController2.m which I have linked to the second view in storyboard along with a gesture that I have linked to the handleSwipeGesture method above.
- (void)viewDidLoad
{
[super viewDidLoad];
UISwipeGestureRecognizer *oneFingerSwipeLeft = [[UISwipeGestureRecognizer alloc]
initWithTarget:self
action:#selector(oneFingerSwipeLeft:)] ;
[oneFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[[self view] addGestureRecognizer:oneFingerSwipeLeft];
}
- (void)oneFingerSwipeLeft:(UITapGestureRecognizer *)recognizer
{
NSLog(#"Swiped left");
// Add your navigation Code Here
}
U can use this if u want to use single finger swipe. IF u want to add two finger Swipe add your
NSUInteger touches = sender.numberOfTouches;
if (touches == 2)
{
if (sender.state == UIGestureRecognizerStateEnded)
{
}
}
above code in the (void)onefingerSwipeLeft method
- (IBAction)handleSwipeGesture:(UISwipeGestureRecognizer *)sender does not need to be an IBAction since it is not hooked up to an IBOutlet. It should return void instead.
Additionally, instead of checking the number of touches once the gesture has already been recognized, you can make two touches a requirement for it to be recognized at all, by using the line:
swipeRightGesture.numberOfTouchesRequired = 2;
Consequently, the method -(void)handleSwipeGesture no longer needs to take an argument, so you can remove the colon from the line where you declare the gesture recognizer.
The final code might look similar to this:
UISwipeGestureRecognizer *swipeRightGesture=[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGesture)];
swipeRightGesture.direction=UISwipeGestureRecognizerDirectionRight;
swipeRightGesture.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:swipeRightGesture];
...
...
- (void)handleSwipeGesture {
ViewController2 *vc2 = [[ViewController2 alloc] initWithNibName:#"ViewController2" bundle:nil];
[self.navigationController pushViewController:vc2 animated:YES];
}
Related
I have been searching for hours ... Needless to say, I'm rather new in programming.
My goal is to display a subview programmatically and to remove it by tapping on it.
Already working: Displaying the subview and removing it by tapping on the view (code below).
Doesn't work - whatever I try: Removing the subview by tapping exclusively on the subview.
Thanks for your help!
My .h-file:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
UIImageView *canvas;
UIImageView *square;
}
#property (nonatomic, strong) UIImageView *canvas;
#property (nonatomic, strong) UIImageView *square;
#property (nonatomic) NSUInteger numberOfTouchesRequired;
- (void)handleSingleTap: (UITapGestureRecognizer *)singleTapGestureRecognizer;
#end
My .m-file:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize canvas;
#synthesize square;
- (void)handleSingleTap: (UITapGestureRecognizer *)singleTapGestureRecognizer {
NSLog(#"tapGesture");
[square removeFromSuperview];
}
- (void)loadView {
canvas = [[UIImageView alloc] initWithFrame: [[UIScreen mainScreen] applicationFrame]];
[canvas setImage:[UIImage imageNamed:#"canvas.png"]];
self.view = canvas;
square = [[UIImageView alloc] initWithFrame:CGRectMake(170, 320, 40, 40)];
[square setImage:[UIImage imageNamed:#"square.png"]];
[self.view setUserInteractionEnabled:YES];
[canvas addSubview:square];
NSLog(#"square");
}
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
NSLog(#"initialize");
singleTapGestureRecognizer.numberOfTapsRequired = 1;
NSLog(#"number of taps");
[self.view addGestureRecognizer:singleTapGestureRecognizer];
NSLog(#"square gestureRecognizer");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
If you want to remove a subview exclusively, when you tap on that view, then you will need to add tap gesture on that view
[square addGestureRecognizer:singleTapGestureRecognizer];
rather than
[self.view addGestureRecognizer:singleTapGestureRecognizer];
Edit
Also you will need to setUserInteractionEnabled to YES of UIImage because UIImage's setUserInteractionEnabled is default to NO.
[square setUserInteractionEnabled:YES];
Instead of
[self.view addGestureRecognizer:singleTapGestureRecognizer];
you should say
[square addGestureRecognizer:singleTapGestureRecognizer];
I have been trying to implement a UITapGestureRecognizer that will dismiss a modal view controller by detecting a tap outside the modal view controller on iOS 8.
The issue I am seeing is that in landscape mode on iPad that the tap gesture is only recognised inside parts of the view controller. My modal view is 380 width x 550 height. When orientation is in landscape (with the home button at the right) the tap gesture is detected inside the bottom half of the modal view. When orientation is in landscape but with the home button at the left hand side the tap gesture is detected inside the top half of the modal view.
Problem 1: The tap gesture is only detected inside the modal view when it should be detected outside.
Problem 2: No detection in some areas of the view in landscape orientation.
Here is the code I used:
UIViewController+DismissOnTapOutside.h
#import <UIKit/UIKit.h>
#interface UIViewController (DismissOnTapOutside)
- (void)registerForDismissOnTapOutside; // Call in viewDidAppear
- (void)unregisterForDismissOnTapOutside; // Call in viewWillDisappear
#end
UIViewController+DismissOnTapOutside.m
#import "UIViewController+DismissOnTapOutside.h"
#import <objc/runtime.h>
static char gestureRecognizerKey;
static char gestureRecognizerDelegateKey;
#interface SimpleGestureRecognizerDelegate : NSObject <UIGestureRecognizerDelegate>
#end
#implementation SimpleGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return [otherGestureRecognizer isMemberOfClass:[UITapGestureRecognizer class]];
}
#end
#interface UIViewController ()
#property (assign, nonatomic) UIGestureRecognizer *gestureRecognizer;
#property (strong, nonatomic) SimpleGestureRecognizerDelegate *gestureRecognizerDelegate;
#end
#implementation UIViewController (DismissOnTapOutside)
- (void)setGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
objc_setAssociatedObject(self, &gestureRecognizerKey, gestureRecognizer, OBJC_ASSOCIATION_ASSIGN);
}
- (UIGestureRecognizer *)gestureRecognizer
{
return objc_getAssociatedObject(self, &gestureRecognizerKey);
}
- (void)setGestureRecognizerDelegate:(SimpleGestureRecognizerDelegate *)delegate
{
objc_setAssociatedObject(self, &gestureRecognizerDelegateKey, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIGestureRecognizer *)gestureRecognizerDelegate
{
id delegate = objc_getAssociatedObject(self, &gestureRecognizerDelegateKey);
if (!delegate)
{
delegate = [[SimpleGestureRecognizerDelegate alloc] init];
self.gestureRecognizerDelegate = delegate;
}
return delegate;
}
- (void)handleDismissTap:(UIGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateEnded)
{
UIView *view = self.navigationController.view ?: self.view;
// Passing nil gives us coordinates in the window
CGPoint location = [gesture locationInView:nil];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// Then we convert the tap's location into the local view's coordinate system
location = [view convertPoint:location fromView:self.view.window];
if (![view pointInside:location withEvent:nil])
{
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
}
- (void)registerForDismissOnTapOutside
{
// This approach is attributed to Danilo Campos:
// http://stackoverflow.com/a/6180584/456434
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleDismissTap:)];
recognizer.delegate = self.gestureRecognizerDelegate;
recognizer.numberOfTapsRequired = 1;
recognizer.cancelsTouchesInView = NO;
self.gestureRecognizer = recognizer;
[self.view.window addGestureRecognizer:recognizer];
}
- (void)unregisterForDismissOnTapOutside
{
[self.view.window removeGestureRecognizer:self.gestureRecognizer];
self.gestureRecognizer = nil;
}
#end
The key method that should set the correct location for tap detection does not seem to be setting the location correctly in landscape orientation in iOS 8:
- (void)handleDismissTap:(UIGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateEnded)
{
UIView *view = self.navigationController.view ?: self.view;
// Passing nil gives us coordinates in the window
CGPoint location = [gesture locationInView:nil];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// Then we convert the tap's location into the local view's coordinate system
location = [view convertPoint:location fromView:self.view.window];
if (![view pointInside:location withEvent:nil])
{
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
}
All of the examples I have looked at on StackOverflow in relation to this issue are adamant that the above code works. But in my case it simply does not detect taps outside the modal view controller.
How can a tap outside a modal be detected in iOS 8 on iPad in landscape orientation?
I want to implement , when swipe bottom to top of screen the keyboard is shown on screen and when swipe from top to bottom on screen the keyboard hides.
Its like iOS 7 effect when we swipe on screen the search textfield and keyboard is shown and when swipe down its hide.
Try this:
//declare a property to store your current responder
#property (nonatomic, assign) id currentResponder;
//in viewDidLoad:
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(resignOnSwipe:)];
[self.collectionView addGestureRecognizer:swipe];
//Implement the below delegate method:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
self.currentResponder = textField;
}
//Implement resignOnSwipe:
- (void)resignOnSwipe:(id)sender {
[self.currentResponder resignFirstResponder]
}
Try Something like this,.. It works fine
In xib,
Add a textfield and hide it. Connect that to your .h file and name it as tf.
In .h file,
Add
#import <UIKit/UIKit.h>
#interface KeyboardDisplay : UIViewController <UIGestureRecognizerDelegate>
{
__weak IBOutlet UITextField *tf;
}
In .m file,
- (void)viewDidLoad
{
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGesture:)];
swipeGesture.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:swipeGesture];
UISwipeGestureRecognizer *swipeGesture2 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGesture:)];
swipeGesture2.direction = UISwipeGestureRecognizerDirectionDown;
[self.view addGestureRecognizer:swipeGesture2];
}
-(void)handleSwipeGesture:(UISwipeGestureRecognizer *) sender
{
//Gesture detect - swipe up/down , can be recognized direction
if(sender.direction == UISwipeGestureRecognizerDirectionUp)
{
[tf becomeFirstResponder];
NSLog(#"Up");
}
else if(sender.direction == UISwipeGestureRecognizerDirectionDown)
{
[tf resignFirstResponder];
NSLog(#"down");
}
}
NOTE : Don't forget to hide the textfield in xib.
I want to have a persistent button in the bottom right corner of my app. During all view transitions, the button should remain static. I'm having trouble deciding what view to add the button to. I know the button ought to be stored in the AppDelegate, but I don't know what other view it would be sense to add it to except the window. One downside of adding it to the window is that when there's an app running in the background (ie Phone), the added status bar padding will push down the window. In general, adding it to the window seems to be a hacky solution -- any thoughts?
Yes, adding it to the UIWindow would be extremely hacky and finicky.
Storyboards
If you're using Storyboards and iOS 5.0 onwards, you should be able to use container views and do something like this:
Here's another picture showing the, rather simplistic, structure of the first View Controller:
The view controller on the left has a container, and then a view which holds the button on top of it. The container indicates that the navigation controller (directly to the right) should appear within itself, that relationship is shown by the =([])=> arrow (formally known as an embed segue). Finally the navigation controller defines its root view controller to the one on the right.
In summary, the first view controller pancakes-in the container view with the button on top, so everything that happens inside has to have the button on top.
Using childViewControllers
aka. The "I hate Storyboards and puppies" mode
Using a similar structure to the Storyboard version, you could create the base view controller with its button, and then, add the view that will become then new "root" of the application, underneath.
To make it clear, let's call the one view controller that holds the button the FakeRootViewController, and the view controller that will be, for all practical purposes, the root of the application: RootViewController. All subsequent view controllers won't even know that there's the FakeRootViewController above everyone else.
FakeRootViewController.m
// The "real" root
#import "RootViewController.h"
// Call once after the view has been set up (either through nib or coded).
- (void)setupRootViewController
{
// Instantiate what will become the new root
RootViewController *root = [[RootViewController alloc] <#initWith...#>];
// Create the Navigation Controller
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:root];
// Add its view beneath all ours (including the button we made)
[self addChildViewController:nav];
[self.view insertSubview:nav.view atIndex:0];
[nav didMoveToParentViewController:self];
}
AppDelegate.m
#import "FakeRootViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
FakeRootViewController *fakeRoot = [[FakeRootViewController alloc] <#initWith...#>];
self.window.rootViewController = fakeRoot;
[self.window makeKeyAndVisible];
return YES;
}
That way, you can have all the benefits of inserting the button on the window, without all the guilt and "Should I really be a programmer?" that it causes.
Potentially you could have 1 main "root" view controller, and all you other view controllers could be child view controllers, with their views as child views. Then they would have their content, and the button would be in the "root" view controller. But this seems just as sketchy and hacky as putting it in the window, and probably less convenient.
I use this button:
#interface UIPopUpButton : UIImageView <UIPopoverControllerDelegate, UIActionSheetDelegate>
{
UIPopoverController* popoverController;
Class popoverClass;
}
- (id) initWithPoint: (CGPoint) point;
- (void) touchesBegan: (NSSet*) touches
withEvent: (UIEvent*) event;
+ (id) buttonAtPoint: (CGPoint) point;
+ (id) buttonAtOriginalPoint;
+ (void) unhighlight;
+ (void) bringButtonToFront;
#property (nonatomic, retain) UIPopoverController* popoverController;
#property (nonatomic, assign) Class popoverClass;
#end
#import "UIPopUpButton.h"
#implementation UIPopUpButton
static UIPopUpButton* button = nil;
static CGPoint originalPoint;
#synthesize popoverClass;
#synthesize popoverController;
+ (id) buttonAtPoint: (CGPoint) point
{
if (button == nil)
{
button = [[UIPopUpButton alloc] initWithPoint: point];
originalPoint = point;
button.popoverClass = [UIPopoverController class];
}
else
{
button.frame = CGRectMake(point.x, point.y, button.frame.size.width, button.frame.size.height);
}
return button;
}
+ (id) buttonAtOriginalPoint
{
return [self buttonAtPoint: originalPoint];
}
+ (void) unhighlight
{
button.highlighted = NO;
}
+ (void) bringButtonToFront
{
[[UIApplication sharedApplication].keyWindow addSubview: [self buttonAtOriginalPoint]];
}
- (id) initWithPoint: (CGPoint) point
{
UIImage* image1 = [UIImage imageNamed: #"topbutton.png"];
UIImage* image2 = [UIImage imageNamed: #"topbutton.png"];
if ((self = [super initWithImage: image1
highlightedImage: image2]))
{
self.userInteractionEnabled = YES;
self.frame = CGRectMake(point.x, point.y, self.frame.size.width, self.frame.size.height);
self.multipleTouchEnabled = NO;
}
return self;
}
- (BOOL) isAppCurrStatus
{
return ([DevToolsClientController sharedInstance].statusOfRootViewController == FrontEndApplication);
}
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
UITouch* touch = [touches anyObject];
if(touch.view == self)
{
if (self.popoverController == nil)
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIActionSheet* actionSheet = [[UIActionSheet alloc] initWithTitle: #"Please choice operation:"
delegate: self
cancelButtonTitle: nil
destructiveButtonTitle: nil
otherButtonTitles: nil];
[actionSheet addButtonWithTitle: #"Cancel"];
actionSheet.cancelButtonIndex = 0;
[actionSheet addButtonWithTitle: #"Button 1"];
actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
[actionSheet setTag: 0];
[actionSheet setDelegate: self];
[actionSheet showInView: [self superview]];
[actionSheet release];
[actions release];
}
else
{
PopoverMenuController* contentViewController = [[PopoverMenuController alloc] init];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController: contentViewController];
popoverController.delegate = self;
[popoverController presentPopoverFromRect: CGRectMake(10.0f, 10.0f, 5.0f, 5.0f)
inView: self
permittedArrowDirections: UIPopoverArrowDirectionAny
animated: YES];
contentViewController.popoverController = self.popoverController;
[contentViewController reloadData];
}
}
else
{
[self.popoverController dismissPopoverAnimated:YES];
self.popoverController = nil;
}
}
[super touchesBegan: touches withEvent: event];
}
#pragma mark UIActionSheetDelegate implementation
-(void) actionSheet: (UIActionSheet*) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex
{
NSNumber* indexAction = [[NSNumber alloc] initWithInt: buttonIndex - 1];
}
- (void) runAction: (NSNumber*) indexAction
{
[DevToolsPopoverMenuController runAction: [indexAction integerValue]];
}
#pragma mark -
#pragma mark UIPopoverControllerDelegate implementation
- (void) popoverControllerDidDismissPopover: (UIPopoverController*) thePopoverController
{
if (self.popoverController != nil)
{
self.popoverController = nil;
}
}
- (BOOL) popoverControllerShouldDismissPopover: (UIPopoverController*) thePopoverController
{
//The popover is automatically dismissed if you click outside it, unless you return NO here
return YES;
}
#end
call:
[UIPopUpButton bringButtonToFront];
My button is always on top.
Try subclassing the UIViewController class and make your own one with the button
Create a singleton object that holds the button so all view controllers can reference it and add it to their subview or add it to the window directly.
SomeClass.h
#property (nonatomic) UIButton *yourButton;
+(SomeClass*)sharedSomeClass;
SomeClass.m
#synthesize yourButton = _yourButton;
-(id)init
{
self = [super init];
if(self)
{
_yourButton = [UIButton new];
//Other settings you want for your button
}
return self;
}
+(SomeClass)sharedSomeClass
{
static SomeClass *sharedSomeClass;
if (!sharedSomeClass)
sharedSomeClass = [[super allocWithZone:nil]init];
return sharedSomeClass;
}
+(void)allocWithZone:(NSZone*)zone
{
return [self sharedSomeClass];
}
If you like you can access the window directly like this:
UIWindow *mainwindow = [[[UIApplication sharedApplication]delegate]window];
import SomeClass.h into your view controllers, and access the button from anywhere
#import "SomeClass.h"
SomeClass *someClass = [SomeClass sharedSomeclass];
UIButton *localButton = someClass.yourButton;
I'm trying to figure out why I get an error when I use locationOfTouch:inView. Eventually I created a new view with just the locationOfTouch call and I still get a SIGABRT whenever I touch the view.
Aside from import statements, here's all the code in my view:
#interface Dummy : UIView <UIGestureRecognizerDelegate> {
UIPanGestureRecognizer *repositionRecognizer;
}
#end
Here's the impl:
#implementation Dummy
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
repositionRecognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self
action:#selector(reposition:)];
[repositionRecognizer setDelegate:self];
[self addGestureRecognizer:repositionRecognizer];
self.backgroundColor = [UIColor grayColor];
}
return self;
}
- (void)reposition:(UIGestureRecognizer *) gestureRecognizer {
[gestureRecognizer locationOfTouch:0 inView:self];
//[gestureRecognizer locationInView:self];
}
#end
If I use locationInView, it works okay. If I use locationOfTouch:inView, the program aborts as soon as the touch ends.
EDIT: On the console, with this class, no error messages are shown. The IDE points to main.m with a SIGABRT. Clicking on 'Continue' results in 'EXC_BAD_INSTRUCTION'. Screenshot available on http://imageshack.us/photo/my-images/849/consolel.png/
This crashes because it assumes that there is a touch zero. You need to confirm there is one first, like so:
- (void)reposition:(UIGestureRecognizer *) gestureRecognizer {
if (gestureRecognizer.numberOfTouches > 0){
CGPoint point = [gestureRecognizer locationOfTouch:0 inView:self];
NSLog(#"%#",NSStringFromCGPoint(point));
}
}
Think of the "locationOfTouch:" part as saying "touchAtIndex:", if the array of touches is empty(When you lift your finger or move it off the screen) then there is no touchAtIndex:0.