Sunday, July 11, 2010

Lock Screen Maker: A resizeble view

The next step is to allow a pinching motion to resize the view.  This is slightly more advanced, but still standard Apple behavior.

I'm going to implement this, but I'm not going to make a very nice set of classes for it.  Apple has implemented a very nice set of UIGestureRecognizer classes for handling all sorts of gestures.  Their code is going to be better than anything I could write.

However one of my target platforms is the current iPhone, which does NOT YET have these.  They are present for the iPad, and will presumably be available in future versions of iPhone OS, but not 3.1.3.

So what I'm going to do is define a simple protocol for my view for receiving the gestures I want.  I will then make a 'gesture handling' class that receives these gestures.   This will either use the UIGestureRecognizer classes if available, or have my own somewhat simpler state machine for this.

I'll put this into my JLFoundation library so it can be available for multiple projects.  This way I can do what I need for now, while maintaining a path forward to switch to the new classes when they become available.

The first draft of this had some problems, but I got it working somewhat.  Next I will separate it out into another class, and debug it.

While I followed examples I found of pinch behavior, the problem I ran into is that my graphics would sometimes detect a 1 touch move on alternating fingers.  This was annoying, I suspect its because either some events are being intercepted by other views, or that I'm only getting an update for one of the views.

The current code for pinching:
Not that for my pinches I want to be able to change size and height as well as overall size.  In addition when the point moves around I want to move the entire rectangle with it.
I overrode the 3 of the 4 major touch events, as well as a few utility routines:

Touches Began


Detect touches.  Find how many touches we have, and find the best view.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"In touchesBegan count =%d",[touches count]);
    // We will do some stuff here.
    // We only support single touches, so anyObject retrieves just that touch from touches
if ([touches count]==2)
{
[self findPinchView:touches];

}else if ([touches count]==1)
{
for (TextDisplayView *view in currentViews)
{
UITouch *touch = [touches anyObject];
if (touch==nil)
{
NSLog(@"NillTouch");
return;
}
CGPoint location = [touch locationInView:self.view ];

// NSLog(@"%@",location);
if (view==nil)
{
NSLog(@"NilView");
return;
}
if (view.view==nil)
{
NSLog(@"NilView.view");
return;
}
if (CGRectContainsPoint(view.view.frame, location))
{
self.currentPinchView=view;
break;
}
}
}    
}



Touches  Moved


Here I'm detecting touches moving, and if I have two touches I do a pinch, and for 1 touch I do a move.  Note that this current code has a bug.  SOmetimes there are two touches on the screen but [touches count] only returns 1.  It seems to flicker between the two.  Note also that sometimes I would get [touches count]==2 without EVER calling TouchesBegan with two touches.  Each of the two touches was a separate touches begin, but never had two.


- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  if ([touches count]==2)
{


NSLog(@"Doing pinch");
NSArray *objects=[touches allObjects];
UITouch *f1=[objects objectAtIndex:0];
UITouch *f2=[objects objectAtIndex:1];
CGPoint p1=[f1 locationInView:self.view];
CGPoint p2=[f2 locationInView:self.view];
CGRect lastRect=[self makeRect:p1 point2:p2];
if (!inPinch)
{
[self findPinchView:touches];
// Nothing more to be done here.
return;
}

// We should be able to scale up evenly this way.
double heightRatio=lastRect.size.height/startRect.size.height;
double widthRatio=lastRect.size.width/startRect.size.width;
double newWidth=imageStartSize.size.width*widthRatio;
double newHeight=imageStartSize.size.height*heightRatio;
// We calculate scales based on the size of the pinch, but apply the scales to the image being
// manipulated.
if (newWidth<5)
newWidth=5;
if (newHeight<5)
newHeight=5;
if (newWidth>self.view.frame.size.width)
newWidth=self.view.frame.size.width;
if (newHeight>self.view.frame.size.height)
newHeight=self.view.frame.size.height;
double dx=CGRectGetMidX(lastRect)-CGRectGetMidX(startRect);
double dy=CGRectGetMidY(lastRect)-CGRectGetMidY(startRect);
self.currentPinchView.view.frame =CGRectMake(imageStartSize.origin.x+dx,
lastRect.origin.y+dy,
newWidth,
newHeight);
}else if (!inPinch) {

NSLog(@"Doing move ");
    UITouch *touch = [touches anyObject];
    
    // If the touch was in the placardView, move the placardView to its location
    
if (currentPinchView!=nil)
{
CGPoint location = [touch locationInView:self.view ];
self.currentPinchView.view.center = location;      
return;
}
    }
}



Touches Ended


In this code I attempt to detect touches ended.  The code here, is from Apples website, and it improved my detection of a real end, however it wasn't completely perfect.

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"touches count %d, event touches for my view %d",[touches count], [[event touchesForView:self.image] count]);
   if ([touches count] == [[event touchesForView:self.image] count])
   {
  NSLog(@"Event %@",event);
  self.currentPinchView=nil;
  NSLog(@"In Touches ended count=%d",[touches count]);
  inPinch=false;
   }
}


Find best view


I want to find the best view for pinch.  In this case it might NOT be a pinch located on the view itself, but around it.  In this case I look at all the current views and find the closest match (in area) for the pinches.  I make use of the rectangle routines for this.

Make rectangle


This utility routine takes two touches and constructs a rectangle for it.
In addition I made the views not respond to touches so that the movement behavior made sense.

No comments:

Post a Comment