How do I pass the user touch from one object to another object? - objective-c

I am developing an application that allows the user at a certain point to drag and drop 10 images around. So there are 10 images, and if he/she drags one image onto another, these two are swapped.
A screenshot of how this looks like:
So when the user drags one photo I want it to reduce its opacity and give the user a draggable image on his finger which disappears again if he drops it outside of any image.
The way I have developed this is the following. I have set a UIPanGesture for these UIImageViews as:
for (UIImageView *imgView in editPhotosView.subviews) {
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(photoDragged:)];
[imgView addGestureRecognizer:panGesture];
imgView.userInteractionEnabled = YES;
[panGesture release];
}
Then my photoDragged: method:
- (void)photoDragged:(UIPanGestureRecognizer *)gesture {
UIView *view = gesture.view;
if ([view isKindOfClass:[UIImageView class]]) {
UIImageView *imgView = (UIImageView *)view;
if (gesture.state == UIGestureRecognizerStateBegan) {
imgView.alpha = 0.5;
UIImageView *newView = [[UIImageView alloc] initWithFrame:imgView.frame];
newView.image = imgView.image;
newView.tag = imgView.tag;
newView.backgroundColor = imgView.backgroundColor;
newView.gestureRecognizers = imgView.gestureRecognizers;
[editPhotosView addSubview:newView];
[newView release];
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:view.superview];
[view setCenter:CGPointMake(view.center.x + translation.x, view.center.y + translation.y)];
[gesture setTranslation:CGPointZero inView:view.superview];
}
else { ... }
}
}
Thus as you see I add a new UIImageView with 0.5 opacity on the same spot as the original image when the user starts dragging it. So the user is dragging the original image around. But what I want to do is to copy the original image when the user drags it and create a "draggable" image and pass that to the user to drag around.
But to do that I have to pass the user touch on to the newly created "draggable" UIImageView. While it's actually set to the original image (the one the user touches when he starts dragging).
So my question is: How do I pass the user's touch to another element?.
I hope that makes sense. Thanks for your help!

Well, you can pass the UIPanGestureRecognizer object to another object by creating a method in your other object which takes the gesture recognizer as a parameter.
- (void)myMethod:(UIPanGestureRecognizer *)gesture
{
// Do stuff
}
And call from your current gesture recognizer using....
[myOtherObject myMethod:gesture];
Not entirely sure I'm understanding your question here fully. :-/

Maybe:
[otherObject sendActionsForControlEvents:UIControlEventTouchUpInside];
Or any other UIControlEvent

In the end I decided to indeed drag the original image and leave a copy at the original place.
I solved the issue with the gesture recognizers I was having by re-creating them and assigning them to the "copy", just like PragmaOnce suggested.

Related

UIImageView Won't Show When ViewController Added As Subview

My main aim is to have one background for all of my ViewControllers. Every ViewController I have has a clear background.
To do this, I have made one UIViewController (called backgroundViewController) that will act as the subview to all my other ViewControllers. It has one UIImageView which displays this particular background. I will then add this backgroundViewController as a subview of all my other ViewControllers.
The problem is - this imageView won't show as a subview!
This is how I display the imageView:
- (void)viewDidLoad
{
if ([musicPlayer playbackState] == MPMusicPlaybackStateStopped) {
UIImage *artworkBackgroundView;
artworkBackgroundView = [UIImage imageNamed:#"noArtworkBackground"];
UIImage *effectImage = nil;
backgroundView.image = effectImage;
[backgroundView setImage:artworkBackgroundView];
backgroundView.image = effectImage;
}
}
- (void) handle_NowPlayingItemChanged: (id) notification
{
if ([musicPlayer playbackState] != MPMusicPlaybackStateStopped) {
// Get artwork for current now playing item
MPMediaItem *currentItem = [musicPlayer nowPlayingItem];
MPMediaItemArtwork *artwork = [currentItem valueForProperty: MPMediaItemPropertyArtwork];
UIImage *artworkBackgroundView = [artwork imageWithSize: CGSizeMake(618, 618)];
if (!artworkImage) {
artworkBackgroundView = [UIImage imageNamed:#"noArtworkBackground"];
artworkImage = [UIImage imageNamed:#"noArtwork"];
}
[backgroundView setImage:artworkBackgroundView];
}
}
As you can see, backgroundView changes each time the music player skips song.
To test that backgroundViewController does show as a subview, I added another imageView and changed its image to a static .png in Interface Builder, and that shows correctly as a subview.
This is how I make it a subview for other ViewControllers:
backgroundViewController *backgroundVC = [[backgroundViewController alloc] initWithNibName:#"backgroundViewController" bundle:nil];
[self.view insertSubview:backgroundVC.view atIndex:0];
What I want is the UIImageView called backgroundView to show up when it is being called as subview.
I have tested that backgroundView does change according to what song is playing, and it works correctly.
What am I doing wrong? backgroundView refuses to show up as a subview?! I've searched a ton about adding ViewControllers as subviews but I can't find a similar problem.
Any help would be much appreciated, thanks! :)
This worked for me:
backgroundViewController *backgroundVC = [[backgroundViewController alloc] initWithNibName:#"backgroundViewController" bundle:nil];
[self addChildViewController:backgroundVC];
[self.view addSubview:backgroundVC.view];
I found the solution here. I need to read more about 'Custom Containers'.

When showing the real time resizing of a pane, it struggles while dragging and top of the pane is hidden after released

I went through several posts on dragging but couldn't find an answer to my problem.
I can use the mouseDown and mouseUp events to track the current positions and redraw the resized pane. What I want is to show the real time movement of the pane. Everytime mouseDragged event is fired, y coordinate of the new location is taken and setFrame is called to redraw. The window seems to flicker and gets stuck finally (title bar goes out of bounds and hidden) as it seems to miss the final events in the run loop.
Is there a way to solve this problem?
The view has been implemented in the following way
NSSplitView (divided into sections left dock, right dock, etc.)
NSView is used to implement a sub view inside the dock
NSTableView is used inside the NSView to hold multiple "panels"
There can be several panels inside this table view (one below another)
I need to resize these panels by dragging the border line. For this I'm using an NSButton in the bottom.(I want to show a thicker separation line there)
Here is the code for mouseDown, mouseUp, mouseDragged callbacks, used to resize the panel
-(void)mouseDown:(NSEvent *)theEvent {
draggingInProgress = YES;
firstDraggingPointFound = NO;
}
-(void)mouseUp:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
}
draggingInProgress = NO;
[_delegate heightChangingEnded]; //draggingInProgress is set to NO
}
-(void)mouseDragged:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
} else {
firstDraggingPointFound = YES;
}
previousDragPosition = point.y;
}
//Delegate method
-(void)heightChanged:(NSNumber *)change {
NSRect f = [[self view] frame];
f.size.height += [change floatValue];
if (f.size.height < 100) {
f.size.height = 100;
}
[[self view] setFrame:f];
[self.panelViewModel setPanelHeight:f.size.height];
if (_heightChangeDelegate) {
[_heightChangeDelegate heightChangedForPanel:self];
}
[[self view] setFrame:f];
}
What would be the problem here?
Is there a better way to do this?
First off, I wouldn’t use the word “Panel” to describe what you want, since an “NSPanel” is a kind of “NSWindow”. Let’s call them “panes.”
Second, I wouldn’t use an NSTableView to contain your panes. NSTableView really isn’t designed for that, it’s for tabular data. You can use an NSStackView, which IS designed for it, or just use raw constraints and autolayout, manually setting the top of each pane to equal the bottom of the previous one.

Objective C: Drawing with Fingers on UIScrollView

I am trying to make a sketch pad app.
I used UIScrollView for paging and UIImageView for the drawing.
I put the UIImageView on top of the scrollView but it's not added to UIScrollView so it will not scroll.
The issue now...
It's not writing when...
[scrollView setScrollEnable:YES];
[scrollView setUserInteractionEnabled:YES];
i need to set it to NO with the use of a button for it to write,
is there a way that i can scroll and write at the same time without using any button??
This is definitely not the correct way of building such an application.
A UIScrollView is meant for scrolling content not for drawing. And you don't need a UIImageView to draw content either, a simple UIView would be enough.
Here you're best bet would be to create one UIScrollView and disable it's scrolling because you'll be handling it with two fingers, while the drawing will be handled pan another gesture recognizer.
UIPanGestureRecognizer *twoFingerScrolling = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(onTwoFingerScroll:)] autorelease];
[twoFingerScrolling setMinimumNumberOfTouches:2];
[twoFingerScrolling setMaximumNumberOfTouches:2];
UIPanGestureRecognizer *oneFingerDraw = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(onOneFingerDraw:)] autorelease];
[oneFingerDraw setMinimumNumberOfTouches:1];
[oneFingerDraw setMaximumNumberOfTouches:1];
[yourScollView addGestureRecognizer:twoFingerScrolling];
[yourScollView addGestureRecognizer:oneFingerDraw];
And later on in your code you can easily process both events, the scrolling:
- (void)onTwoFingerScroll:(UIPanGestureRecognizer*)sender
{
// Calculate the content offset from the shifting that occured
//[yourScrollView setContentOffset:theContentOffset]
}
And the drawing (which can be done by the Quartz Tookit)
- (void)onOneFingerDraw:(UIPanGestureRecognizer*)sender
{
// Processing the drawing by using comparing:
if (sender.state == UIGestureRecognizerStateBegan)
{ /* drawing began */ }
else if (iRecognizer.state == UIGestureRecognizerStateChanged)
{ /* drawing occured */ }
else if (iRecognizer.state == UIGestureRecognizerStateEnded)
{ /* drawing ended /* }
}
Hope this helps.

detect long press on UINavigationItem's back button

I want to add functionality to my back buttons through my UINavigationController-based app where long-pressing the back button will pop to root. However, I can't figure out where to attach the gesture recognizer. Do I subclass UINavigationBar and try and detect if the long press is in the left button region?
I've heard of people adding similar functionality before. Anyone have any ideas?
I know this question is old, but I came up with a solution. Instead of trying to add the gesture recognizer to the button itself (which would be ideal), I added it to the self.navigationController.navigationBar and then in the action method, use the locationInView to see if I'm over the back button. I wasn't entirely sure about how to identify the back button precisely, so I'm clumsily just grabbing the the first subview with an x coordinate less than some arbitrary value, but it seems promising. If someone has a better way to identify the frame of the back button, let me know.
- (void)longPress:(UILongPressGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
// set a default rectangle in case we don't find the back button for some reason
CGRect rect = CGRectMake(0, 0, 100, 40);
// iterate through the subviews looking for something that looks like it might be the right location to be the back button
for (UIView *subview in self.navigationController.navigationBar.subviews)
{
if (subview.frame.origin.x < 30)
{
rect = subview.frame;
break;
}
}
// ok, let's get the point of the long press
CGPoint longPressPoint = [sender locationInView:self.navigationController.navigationBar];
// if the long press point in the rectangle then do whatever
if (CGRectContainsPoint(rect, longPressPoint))
[self doWhatever];
}
}
- (void)addLongPressGesture
{
if (NSClassFromString(#"UILongPressGestureRecognizer"))
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.navigationController.navigationBar addGestureRecognizer:longPress];
[longPress release];
}
}
I believe UIGestureRecognizers can only be added to UIViews and subclasses of UIViews.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIView_Class/UIView/UIView.html
The back button is a UIBarButtonItem that descends from NSObject. Therefore, you won't be able to attach a gesture recognizer to a standard back button using
UILongPressGestureRecognizer *longPressGesture =
[[[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(longPress:)] autorelease];
[self.navigationItem.backBarButtonItem addGestureRecognizer:longPressGesture];
You can however add a custom view to a UIBarButtonItem. A custom view could just as easily be a UIView, UIButton, UILabel, etc.
Example:
UIView *myTransparentGestureView = [[UIView alloc] initWithFrame:CGRectMake(0,0,40,30)];
[myTransparentGestureView addGestureRecognizer:longPressGesture];
[self.navigationItem.backBarButtonItem setCustomView:myTransparentGestureView];
// Or you could set it like this
// self.navigationItem.backBarButtonItem.customView = myTransparentGestureView;
[myTransparentGestureView release];
You have to be careful however, since setting properties on backBarButtonItem applies to the next view that you push. So if you have view A that pushes to view B and you want the gesture to be recognized when you tap back in view B. You must set it up in view A.
I followed a slightly different path, figured I'd share it. The above answers are fine, but really, if the long press is in the leading 1/3 of the nav bar, that's good enough for me:
- (void)longPress:(UILongPressGestureRecognizer *)gr
{
NSLog(#"longPress:");
UINavigationBar *navBar = [self navigationBar];
CGFloat height = navBar.bounds.size.height;
CGPoint pt = [gr locationOfTouch:0 inView:navBar];
//NSLog(#"PT=%# height=%f", NSStringFromCGPoint(pt), height);
if(CGRectContainsPoint(CGRectMake(0,0,100,height), pt)) {
[self popToViewController:self.viewControllers[0] animated:YES];
}
}
Here's my solution:
In appDelegate (the "owner" of the nav bar in my app), In applicationDidFinishLaunchingWithOptions:
Get the nav bar view and add the gesture recognizer to the whole view:
// Get the nav bar view
UINavigationBar *myNavBar = nil;
for (UIView *view in [self.window.rootViewController.view subviews]) {
if ([view isKindOfClass:[UINavigationBar class]]) {
NSLog(#"Found Nav Bar!!!");
myNavBar = (UINavigationBar *)view;
}
}
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:#selector(backButtonLongPress:)];
[myNavBar addGestureRecognizer:longPress];
NSLog(#"Gesture Recognizer Added.");
Then in appDelegate, in -(void) backButtonLongPress:(id) sender
Check to see if the gesture occurs within the frame of the back button:
if ([sender state] == UIGestureRecognizerStateBegan) {
// Get the nav bar view
UINavigationBar *myNavBar = nil;
for (UIView *view in [self.window.rootViewController.view subviews]) {
if ([view isKindOfClass:[UINavigationBar class]]) {
NSLog(#"Found Nav Bar!!!");
myNavBar = (UINavigationBar *)view;
}
}
// Get the back button view
UIView *backButtonView = nil;
for (UIView *view in [myNavBar subviews]) {
if ([[[view class] description] isEqualToString:#"UINavigationItemButtonView"]) {
backButtonView = view;
NSLog(#"Found It: %#", backButtonView);
NSLog(#"Back Button View Frame: %f, %f; %f, %f", backButtonView.frame.origin.x, backButtonView.frame.origin.y, backButtonView.frame.size.width, backButtonView.frame.size.height);
}
}
CGPoint longPressPoint = [sender locationInView:myNavBar];
NSLog(#"Touch is in back button: %#", CGRectContainsPoint(backButtonView.frame, longPressPoint) ? #"YES" : #"NO");
if (CGRectContainsPoint(backButtonView.frame, longPressPoint)) {
// Place your action here
}
// Do nothing if outside the back button frame
}

How to pass scrolling input to a different view

This site really is awesome.
I have what is hopefully a simple question this time. I would like to pass any scrolling input from the user (could be wheel, touchpad, etc) to an NSScrollView which contains my own subviews.
At the moment if the user scrolls just on the documentView (outside of my subviews' frames) the scroll works normally but if they scroll while the cursor is over a subview nothing happens. So basically I'd like to have the subview recognise the scroll event and pass it back to the scroll view.
Any help is greatly appreciated.
Cheers.
EDIT:
Here is the code I'm using to add the subviews to the documentView
_milestoneView and _activityView are both NSView subclasses which have a corresponding nib (created with instantiateNibWithOwner and objects hooked up accordingly) they contain a NSTextField, PXListView and some have a NSProgressIndicator.
-(void)useProject:(NSNumber *)projectId
{
[self resetView];
NSRect bounds = [[self view] bounds];
NSRect defaultFrame = NSMakeRect(20, NSMaxY(bounds)-93, NSMaxX(bounds)-20, 50);
//Prepare the milestone view
if (_milestoneView == nil)
_milestoneView = [MilestoneView milestoneViewFromNibWithFrame:defaultFrame withProject:[BCLocalFetch projectForId:projectId]];
[_milestoneView reloadList];
//Prepare the activity view
if (_activityView == nil)
_activityView = [ActivityView activityViewFromNibWithFrame:defaultFrame withProject:[BCLocalFetch projectForId:projectId]];
[self refresh];
}
I then use the refresh method to reposition them as the content sizes vary so I wanted to have a separate method.
-(void)refresh
{
//Position the milestones first
[_milestoneView setFrameOrigin:NSMakePoint(20, NSMaxY([[self view] bounds])-[_milestoneView frame].size.height-60)];
if ([[_milestoneView milestones] count] > 0)
[[self view] addSubview:_milestoneView];
//Now the activity view
[_activityView setFrameOrigin:NSMakePoint(20, [_milestoneView frame].origin.y-[_activityView frame].size.height-20)];
[[self view] addSubview:_activityView];
[self autosizeView];
}
-(void)autosizeView
{
//Resize the view to accommodate all subviews
NSRect oldFrame = [[self view] frame];
CGFloat lastY = [_activityView frame].origin.y;
if (lastY < 0) {
CGFloat newHeight = oldFrame.size.height + (-lastY);
[[self view] setFrameSize:NSMakeSize(oldFrame.size.width, newHeight)];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"BBContentDidResizeNotification" object:self];
}
Ok so I came back to the issue and finally got it worked out. The implementation was actually quite simple.
I added a property to PXListView to point to the NSScrollView that is to be scrolled.
I then implemented NSResponder's scrollWheel: method like this:
-(void)scrollWheel:(NSEvent *)theEvent
{
//Pass scrolling to the superview
[_scrollHandler scrollWheel:theEvent];
}
And all is well!