前几天做即时通上表情选择的功能,结果不幸用了Windows上的老套路,思路错步步错,越写越感觉进入了死胡同,白白浪费了一下午,然后突然开窍,轻松解决,有必要记录下相关的细节。
情景很简单,做一个选择表情的界面,功能无非是点击弹出一个ScrollView,上面排列一些表情,然后通过拖曳或者点击的方式选择表情。具体可以参考iPad版QQ的操作。
一开始的思路很Windows化,既然UIView有touchesBegan相关的4种事件,基本可以映射成Windows上的MouseDown,MouseMove和MouseUp,那么很简单,在ScrollView的SuperView上响应这四个事件即可:当touchesBegan的时候通过hitTest找到ScrollView中的相应表情,并生成其拷贝,然后在touchesMoved的时候不断改变这份拷贝的位置,最后在touchEnded的时候销毁相应拷贝,并发送选择的通知给接收端即可。但实施的时候碰到了各种问题:
于是貌似陷入了死胡同。不过在查阅各种资料的时候,突然发现了一种特别的做法(其实应该算是iOS下比较常规的做法,不过从Windows过来的童鞋可能会觉得比较奇怪):自定义ScrollView内的表情,以它为First Responder去响应触摸事件即可。看了DEMO,突然回忆起一早看的官网上看到的一段话,恍然大悟:
触摸信息有时间和空间两方面,时间方面的信息称为阶段(phrase),表示触摸是否刚刚开始、是否正在移动或处于静止状态,以及何时结束—也就是手指何时从屏幕举起(参见图3-1)。触摸信息还包括当前在视图或窗口中的位置信息,以及之前的位置信息(如果有的话)。当一个手指接触屏幕时,触摸就和某个窗口或视图关联在一起,这个关联在事件的整个生命周期都会得到维护。如果有多个触摸同时发生,则只有和同一个视图相关联的触摸会被一起处理。类似地,如果两个触摸事件发生的间隔时间很短,也只有当它们和同一个视图相关联时,才会被处理为多触击事件。
和Windows不同,由于iOS设备的特殊性,在事件处理方面简单了很多,从Windows的角度来说,iOS上的一个触摸事件基本等同于Windows上对某个控件触发某种消息后进行Capture直到最终ReleaseCapture这么一个过程,只要一个事件开始就不会做HitTest,事件是和刚开始确定的First Responder所关联。这样一来实现拖曳就非常简单,主要自定义一个继承自UIImageView或者UIButton用于表情显示的控件,并重写其触摸事件的实现即可:
触摸事件发生:初始化拖曳状态,对表情对象进行拷贝并显示:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
scrollView.scrollEnabled = NO;
isDraging = NO;
//生成自己的clone
self.clone = [Emoticon getEmoticonFromIndex:iconIndex];
CGPoint newLocation = [[touches anyObject] locationInView:parentView];
CGFloat startX = newLocation.x - 128 / 2;
CGFloat startY = newLocation.y - 128 / 2;
CGRect rect = CGRectMake(startX, startY, 128, 128);
[clone setFrame:rect];
[parentView addSubview:clone];
clone.center = newLocation;
[super touchesBegan:touches withEvent:event];
}
移动中:移动拷贝对象,并设置拖曳状态 (设置这个状态是为了区分点击事件和拖曳事件)
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
isDraging = YES;
CGPoint newLocation = [[touches anyObject] locationInView:parentView];
clone.center = newLocation;
[super touchesMoved:touches withEvent:event];
}
触摸事件结束:先响应父类事件(区分点击和拖曳事件),设置拖曳状态,并发送通知
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
CGPoint newLocation = [[touches anyObject] locationInView:parentView];
//如果不在当前ScrollView内就认为已经选了
CGRect scrollViewRect = scrollView.frame;
if (!CGRectContainsPoint(scrollViewRect, newLocation))
{
[self postNotification:YES];
}
[self cleanClone];
}
这样一个简单的表情拖曳的功能就算完成了,详细的代码可以参考这里:点此下载 。(XCode4.2)