Search code examples
objective-ccore-animationcore-graphicsuigesturerecognizernsnotificationcenter

Changing UIView layout while GestureRecognizers happen from MainView using NSNotificationCenter


I have some views added as subviews and handling the UILongPressGestureRecognizer (is this the correct gesture for recognizing that I have put my finger in a UI element??) and UIPanGestureRecognizer to move views in the screen (refer to my previous question to have an idea of what I'm building -> SO)

I would like to add animations when put the finger (flip, little zoom like) and then while moving the view to rearrange the other views and finally place it and reorder the views.

My first problem is that when LongPress the view the gesture is recognized but while this gesture has not ended I can't move the View to the screen and reposition. How could I solve this one? LongPress a UI element (UIMyView) do any animation for this event and then without take away my finger start moving it around like the PanGesture is already working.

Created a UIView subclass and added the desired UIGestureRecognizers in it.

I used the NSNotificationCenter to notify the MainViewController that gestures happened so I can change the layout of my view (is this correct approach, or I could push to my superview controller that this gesture happened somehow else, like pushing the event to the next responder)?

I use drawTextInRect method to draw a character to the size of the view, thats why I can't just change the layout of the View inside of it.

In the MainViewController I call the setNeedsDisplay in the NSNotification observer selector for the specific view, but that is not working while dragging it just shows an empty white View, like the view changed as I need but didn't shown yet cause of thread pause or something.

When PanGesture ends I notify the MainViewController again and call the setNeedsDisplay again, but now it works except the background color that I set (and it is set), stays in the previous condition which should show while dragging, but the character draws again correctly.

My mind goes into threads!?

[EDIT] Some code sample:

   // Inside the UIMyView
-(void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
     [[NSNotificationCenter defaultCenter] postNotificationName:@"MyViewNotification" object:self];
    // Here I will put my animations, not yet proceed with them
    // Code for lastLocation
    isViewTouched = YES;
    [self setNeedsDisplay];
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
     // code for moving the View with finger
}

-(void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
  isViewTouched = NO;
  [self setNeedsDisplay];
}

-(void) drawLayout
{
  UILabel* labelToDraw = [[UILabel alloc] init];
  labelToDraw.text = @"1";
  labelToDraw.font = [self getFont];
  labelToDraw.textcolor = [UIColor blackColor];
  self.backgroundColor = [UIColor whiteColor];
  [labelToDraw drawTextInRect:self.bounds];
}
// The same method with other color values and text exist for another layout.
// Called inside of drawRect:(CGRect)rect when calling setNeedsDisplay as I know and it is

-(void) drawRect:(CGRect)rect
{
  if (isViewTouched) [self drawLayout];
  else [self drawLayoutMoving];
}
    //Superview Controller notified
-(void) handleMyViewNotification:(NSNotification*)notification
{
  myViewMoving = [notification object];
  [self performSelectorInbackground:@selector(manageArrayOfMyViews) withObject:nil];
}

-(void) manageArrayOfMyViews
{
  // animate and reorder the array of views not touched
}

I'm new in Objective-C so I'm missing something here, and I would like to know if my approach is correct in general.

Thank you.


Solution

  • Gesture recognizers don't seem to be the simplest way to solve your problem. Try using

    - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    

    to detect the start of your touch and play starting animations. Then

    - (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    

    to drag your view. And

    - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    

    to make the final setup, when the finger is no more on screen.

    For example:

    - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
       UITouch *touch = [touches anyObject];
       if ([touch.view isKindOfClass: [MyView class]]) {
          //play animation on touch.view
          lastLocation = [touch locationInView: self.view];
       }
    }
    
    - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
       UITouch *touch = [touches anyObject];
        if ([touch.view isKindOfClass: [MyView class]]) {
           CGPoint location = [touch locationInView: self.view];
    
           CGFloat xDisplacement = location.x - lastLocation.x;
           CGFloat yDisplacement = location.y - lastLocation.y;
    
           CGRect frame = touch.view.frame;
           frame.origin.x += xDisplacement;
           frame.origin.y += yDisplacement;
           touch.view.frame = frame;
       }
    }
    

    [EDIT]

    As I suppose from your code, you're trying to draw a label with number on top of the view you want to move. You don't need to implement a custom drawRect method, and your task could be done easier. When you create a view, which you'll later be dragging, you add a UILabel as a subview and customize it the way you want:

    UIView* viewToDrag = [[UIView alloc] initWithFrame: // your frame];
    UILabel* subLabel = [[UILabel alloc] initWithFrame: CGRectMake(0, 0, viewToDrag.frame.size.width, viewToDrag.frame.size.height)];
     subLabel.tag = 0xff; // example tag, you can define it 
     [self customizeStaticLabel: subLabel]; //subroutine to customize your usual label appearance: set text, font, etc
    [viewToDrag addSubview: subLabel];
    [self.view addSubview: viewToDrag];
    
    //insert release calls for subLabel and viewToDrag if you're NOT using ARC
    

    And that's all, your label is now a child of your draggable view. And you don't need any notifications and don't need to call setNeedsDisplay.

    When you drag a view, you can get your view's subLabel changed:

    UILabel* labelToChange = (UILabel*)[touch.view viewWithTag: 0xff]; //this will get a subview label
    [self customizeDraggedLabel: labelToChange];
    

    In touchesEnded, you need to get your label back to normal state, so

    UILabel* labelToChange = (UILabel*)[touch.view viewWithTag: 0xff];
    [self customizeStaticLabel: labelToChange];
    

    And the subroutines for changing label appearance, just for example:

    -(void) customizeStaticLabel: (UILabel*) label{
       label.textColor = [UIColor redColor];
    }
    
    -(void) customizeDraggedLabel: (UILabel*) label{
       label.textColor = [UIColor blackColor];
    }