Handling touches within UIScrollview - ios

I have a view that contains a UIScrollView. The UIScrollView contains an UIImageView (only one for now but there will be more latter). I want to be able to drag the UIImageView out of the UIScrollView and into the superview.
I have reviewed every similar question and example I can find online and still cant get my head around how this works.
At first I couldn't get touches to register all whithin the scrollview. After a lot of searching I have found a way to get touchesbegan to register as follows:
First I created a new view based project and in the auto-created "ViewController.h"
I created an IBOutlet named slider.
Then in the auto-created "ViewController.m" I created the UIScrollview.
In IB I added a UIScrollView to the storyboard then connected it to the "slider" IBOutlet.
Next I added class file to the projects. This new class is a subclass os UIImageView. I named this class "DragableImage" In the
Then in IB I dropped an image into the UIScrollview clicked on it and changed the class to "DragableImage"
In the DragableImage.m I voided touches began and simply told it to become hidden if clicked.
This works and the touch registers and makes the image disappear but how do I now add the new image to the main view and have it follow my finger?
This is what I have in the way of code:
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
IBOutlet UIScrollView *slider;
}
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[slider setScrollEnabled:YES];
[slider setContentSize:CGSizeMake(306, 1560)];
[slider setShowsVerticalScrollIndicator:NO];
slider.canCancelContentTouches = NO;
slider.delaysContentTouches = NO;
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
#end
DragableImage.h
#import <UIKit/UIKit.h>
#interface DragableImage : UIImageView
#end
DragableImage.m
#implementation DragableImage
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.hidden = YES;
}
#end
How to make this work?

There is one obvious problem that can arise here. Namely, how to tell the difference between dragging to scroll and dragging an image. The way I would handle this is to setup a tap and hold gesture recognizer (http://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizers/GestureRecognizers.html).
Once you detect a hold you could set it up so that further touches/gestures get routed to you drag and drop code. You can find a nice tutorial for that here:
http://www.ancientprogramming.com/2012/04/05/drag-and-drop-between-multiple-uiviews-in-ios/

Related

Connecting a UIScrollView to a UIView class

I'm currently creating a "slide-show" of pictures that the user can scroll through. Following a guide, I made it so that the UIScrollView I am using shows the edges of the previous and next pictures as the user scrolls along. This comes with a side-effect of the user not being able to scroll if he touches on one of the edges of the pictures, because these edges are technically not within the border of the UIScrollView itself. In order to compensate for this, I am going to create a UIView in which I will embed the UIScrollView. The UIVew will be extend the entire width of the page so that the user can scroll when touching the edges of the pictures. To do this, I need to connect the UIScrollView to the code of the UIView through an IBOutlet. Usually this is simply accomplished by Ctrl-clicking on the UIScrollView and dragging to the code. However, this does not seem to work for me and I am stumped as to why.
Here is a screenshot of the environment I am dealing with when I try to ctrl-click on the UIScrollView and drag to the code to create an IBOutlet (it simply doesn't give the option to create anything).
Here is a screenshot of what running the simulator produces. If i try to click and drag where my mouse currently is, it doesn't scroll, which is the problem I am trying to correct.
It is because you should link your storyboard view to UIView class. You can choose your ScrollViewController class in custom class settings. I added the sample jpg
what you are looking for is not that 'easy' by just connecting the two.
There are two options:
1) In the future you should use a UICollectionView and implement the paging behaviour yourself, that is the cleanest code I think.
NEVER MIND THE SECOND OPTION - I DIDN'T SEE YOU ACTUALLY ALREADY USED THIS
2) To directly answer your question:
You have to subclass the UIView that contains the UIScrollView like this:
header:
#import <UIKit/UIKit.h>
#interface SCTouchForwardView : UIView
#property (nonatomic, weak) IBOutlet UIView* receiver;
#property (nonatomic, assign) BOOL force;
#end
Implementation:
#import "SCTouchForwardView.h"
#implementation SCTouchForwardView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if (![result isKindOfClass:[SCTouchForwardView class]]) {
return result;
}
if (result==self) {
UIView *result = [self.receiver hitTest:[self convertPoint:point toView:self.receiver] withEvent:event];
if (result) {
return result;
}else{
if (self.force) {
return self.receiver;
}
return nil;
}
}
return nil;
}
#end
When you want to use this, you just have to Right-Click-Drag from the container to the UIScrollview and select 'receiver' and what you tried to do will work!

When adding UIView instantiated with an .xib to the Storyboard buttons don't work and background color can't be changed

I am working on an iOS project and wanted to include a UIView that is reused on multiple screens in the application (appearing at the bottom of different UIViews). I am using a storyboard for the UI work so far but created an .xib file to be used by the reusable view (playerView in code below).
The view gets added but the button I have added to the View is unresponsive, also my background color cannot be changed on the view. I have tried to set background color programmatically and in the .xib with no luck. Very weird symptoms and I tried to instantiate the view from #5 of this article and probably did something wrong. I dont fully understand everything in the article which makes me nervous - for instance my showSubclassedView method returns IBAction but I just call the function name in code and dont use a button (I did hook up the buttons in the view as the article described though).
Here is the code:
EventViewController.m (where I trry and add PlayerView)
#import "EventViewController.h"
#import "PlayerView.h"
#interface EventViewController ()
-(IBAction)showSubclassedView;
#end
#implementation EventViewController
-(IBAction)showSubclassedView
{
[PlayerView presentInViewController:self];
}
PlayerView.h (.h for reusable view)
#import <UIKit/UIKit.h>
#import "Utils.h"
#class PlayerView;
#protocol PlayerViewDelegate
-(void)playerViewTouchedUp:(PlayerView*) playerView;
-(void)playerViewDidDismiss:(PlayerView*) playerView;
#end
#interface PlayerView : UIView
+(void)presentInViewController:(UIViewController<PlayerViewDelegate>*) playerView;
-(IBAction)viewTouchedUp;
-(IBAction)dismiss;
#end
PlayerView.m (.m for reusable view)
#import "PlayerView.h"
#interface PlayerViewOwner : NSObject
#property(nonatomic,weak) IBOutlet PlayerView *playerView;
#end
#implementation PlayerViewOwner
#end
#interface PlayerView ()
#property (nonatomic, weak) UIViewController <PlayerViewDelegate> *delegateViewController;
#end
#implementation PlayerView
+(void)presentInViewController:(UIViewController<PlayerViewDelegate> *)viewController
{
PlayerViewOwner *owner = [PlayerViewOwner new];
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:owner options:nil];
owner.playerView.delegateViewController = viewController;
[viewController.view addSubview:owner.playerView];
}
-(IBAction)viewTouchedUp
{
//forward to delegate
NSLog(#"you clicked a button");
[self.delegateViewController playerViewTouchedUp:self];
}
-(IBAction)dismiss
{
[self removeFromSuperview];
// Forward to delegate
[self.delegateViewController playerViewDidDismiss:self];
}
#end
PlayerView.xib has a UIbutton on it that connects it to viewTouchedUp method in PLayerView.m
Is there anything I did wrong in the code above? Is this the best way to do a reusable view to display on other views?
Thank you!
Here's a workaround I found after not being able to solve this.
New implementation is from this article
I reverted my changes from my initial question and implemented this. It lacks the nice protocol separation for talking back and forth and I might need something like this later but I think now I can still implement a protocol to solve this.
At least with this solution I have usable items in my view and a background that changes!

How to setup a subclass of UIScrollView to respond to touch events?

I recently had a problem regarding not being able to register touches events (touchesMoved, etc) from within a UIScrollView. After talking with lots of people and reading tons of posts, it turns out UIScrollView does't accept touches itself, and instead the touches need to be passed to a UIScrollView subclass.
I am very new to objective-c, and am having a terrible time wrapping my head around how subclasses are defined (in separate .h/.m files or somehow in #interface or something), as well as how iOS deals with responder chains.
Please let me describe my exact situation, and anyone willing to explain what I need to do in noob-speak will be really appreciated.
I have a single page in my app, and I changed it's default UIView to UIScrollView, so if I inspect it in InterfaceBuidler all I see is my SampleViewController and UIScrollView. I linked my UIScrollView to the #Interface of SampleViewControler.h like this:
#import <UIKit/UIKit.h>
#interface SampleViewController : UIViewController
{
}
#property (strong, nonatomic) IBOutlet UIScrollView *mainScroller;
#end
And I referenced my UIScrollView in SampleViewController.m like this:
#import "SampleViewController.h"
#interface SampleViewController ()
#end
#implementation SampleViewController
#synthesize mainScroller = _mainScroller;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Movement!");
}
-(void)viewWillAppear:(BOOL)animated
{
[_mainScroller setContentOffset:CGPointMake(0,0) animated:YES];
}
If I change the class of UIScrollView to UIView, the touches are detected and I fire off a few NSLog messages. However, once UIView is set back to UIScrollView, touches are ignored.
From what I understand I need to setup a subclass of UIScrollView with the code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Movement!");
}
...and then change my SampleViewController.m to have the code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self.nextResponder nextResponder] touchesMoved:touches withEvent:event];
}
However, I really don't understand 1) how to setup such a subclass, and 2) why this would work, since I don't see why [[self.nextResponder nextResponder] touchesMoved:touches withEvent:event]; would ever be called in the first place from within SampleViewController.m, since my similar NSLog(#"Movement!"); statement is never executed.
I have tried every tutorial I could find, and am turning toward to the expertise of SE! Thanks.
You're thinking about this wrong. If you want to know when a scrollView is moving, there's no need to subclass it. iOS has set up all the methods you need inside of the UIScrollViewDelegate. IF however you want to say, pick up touchEvents for items based inside the scrollView, then that is perfectly okay. You need to then just subclass the items inside the scrollView and pick up the touchEvents inside the respective classes though. But for just standard scrollView methods you should set it up as so:
.H
#import <UIKit/UIKit.h>
#interface SampleViewController : UIViewController<UIScrollViewDelegate>
{
}
#property (strong, nonatomic) IBOutlet UIScrollView *mainScroller;
#end
.M
#import "SampleViewController.h"
#interface SampleViewController ()
#end
#implementation SampleViewController
#synthesize mainScroller = _mainScroller;
-(void)viewDidLoad
{
self.mainScroller.delegate=self;//Adopt the delegate for the scrollView..this is the important line
}
Then just implement the UIScrollViewDelegateMethods to grab movement etc. Undefined behavior will occur when trying to pick up touchEvents inside a scrollView/tableView, since the scrollView itself already implements those touchEvents behind the scenes
Look at all the methods on the Developer site and implement them in your .M file. Since you've adopted the delegate in viewDidLoad, these will be your scrollView call backs. I've listed a few of them before but go ahead and look on the UIScrollViewDelegate Protocol site
– scrollViewDidScroll:
– scrollViewWillBeginDragging:
– scrollViewWillEndDragging:withVelocity:targetContentOffset:
– scrollViewDidEndDragging:willDecelerate:
– scrollViewShouldScrollToTop:
– scrollViewDidScrollToTop:
– scrollViewWillBeginDecelerating:
– scrollViewDidEndDecelerating:
I posted an answer to a similar question a few days ago that might help you as well. See this question and my answer.
It looks like you're close to getting it working, but you've gotten a bit confused about where to put the touchesMoved methods.
In your subclass of UIScrollView, you should have:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self.nextResponder nextResponder] touchesMoved:touches withEvent:event];
}
And in your SampleViewController.m file:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Movement!");
}
(You have these two around the other way in your question.)

Button doesn't work after Xcode update to version 4.5

I have updated my Xcode and suddenly all Buttons, which are connected in the storyboard to the selectors, doesn't work anymore. All programmatically coded Buttons and gesture recognizers work. Partly, they are calling the same IBActions.
What have I done...
Just add a Button and a Label to the View in the Storyboard. In the View Controller .h I added a Outlet to the Label and declare the IBAction.
The files:
.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *myLabel;
-(IBAction)button_pressed:(id)sender;
-(IBAction)swipe_active:(id)sender;
#end
.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize myLabel;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UISwipeGestureRecognizer *swipe_left = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipe_active:)];
swipe_left.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:swipe_left];
}
-(IBAction)button_pressed:(id)sender{
myLabel.text=#"Button is Running";
}
-(IBAction)swipe_active:(id)sender{
myLabel.text=#"Swipe is Running";
}
#end
And just the Button does not work.
StoryBoard: https://i.stack.imgur.com/1hruM.png
I had this same exact problem after upgrading to xcode 4.5. The IBActions appear as though they are set up correctly in IB but if you look at the .h and .m files, you'll see they're not. If you go to your storyboard and click on your view controller, look under the outlets and you'll see a tab for "Received Actions". Hook up your controls to your actions there and they'll work again.
I have been seeing the exact same issue for many of our apps. In all of my cases it was fixed by reconnecting the action in Interface Builder.

Having delegate problems for my Navigation controller

so i have a navigation controller, and in it i have a view and a scrollview embedded in that view. My goal is to make my program scroll to a location when it is close to it.
to do this i added to outlets to my vc. one is the scroll view and the other is the view. The view is just a big rectangle about twice as wide as the iphones screen and slightly higher. My scroll view scrolls only horizantally, and whenever i lift my finger from scrolling, my goal is if the iphones bounds are within the views bounds. I want the scroll view to manually scroll to the middle of that view. (if you can think of a game menu in angry birds or cut the rope, when the user is scrolling between level packs horizantally, when the user lets go from scrolling it sort of zaps into place to the level the user let go by, this is what i want to mimick).
so, heres my code for both my view and my vc. The only thing i did with storyboards is that i hooked up the two outlets, and set the size of my view.
(the level packs is the vc and the other one is the view).
#import <UIKit/UIKit.h>
#import "HorizantalLockingView.h"
#interface LevelPacks : UIViewController <ViewDelegate>
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (weak, nonatomic) IBOutlet HorizantalLockingView *lockingView;
#end
#import "LevelPacks.h"
#implementation LevelPacks
#synthesize scrollView=_scrollView;
#synthesize lockingView = _lockingView;
-(void)viewDidAppear:(BOOL)animated
{
CGSize size;
size.width=825;
size.height=460;
self.scrollView.contentSize=size;
self.scrollView.contentOffset=CGPointMake(0, 0);
}
-(void)viewDidLoad
{
[super viewDidLoad];
self.lockingView.delegate=self;
}
- (void)viewDidUnload
{
[self setLockingView:nil];
[super viewDidUnload];
self.scrollView=nil;
// Release any retained subviews of the main view.
}
#end
#import <UIKit/UIKit.h>
#protocol ViewDelegate
#property (nonatomic, weak) UIScrollView *scrollView;
#end
#interface HorizantalLockingView : UIView
#property (nonatomic, weak) IBOutlet id <ViewDelegate> delegate;
#end
#import "HorizantalLockingView.h"
#implementation HorizantalLockingView
#synthesize delegate=_delegate;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGRect location=self.superview.bounds;
NSLog(#"dkfjdkjf");
if (CGRectContainsRect(self.frame, location))
{
[self.delegate.scrollView scrollRectToVisible:CGRectMake(284, 190, 157, 119) animated:YES];
}
}
#end
o, and i did the nslog earlier to see if the method would even do anything, and the nslog never even ran, so obviously somethings wrong.
Im kind of new to this, any help would really be great!
Enabling paging will automatically do this for you.
scrollView.pagingEnabled=YES;
Also adding a UIPageControl and using the UIScrollView delegate methods to update the page control will add a good usability

Resources