我正在开发一个屏幕上有 9 个视图的应用程序,我希望用户以他们想要的方式连接视图,并将他们的序列记录为密码。但我不知道我应该使用哪个手势识别器。
我应该使用 CMUnistrokeGestureRecognizer 还是几个滑动手势的组合或其他任何东西?谢谢。
我正在开发一个屏幕上有 9 个视图的应用程序,我希望用户以他们想要的方式连接视图,并将他们的序列记录为密码。但我不知道我应该使用哪个手势识别器。
我应该使用 CMUnistrokeGestureRecognizer 还是几个滑动手势的组合或其他任何东西?谢谢。
您可以使用 a UIPanGestureRecognizer
,例如:
CGFloat const kMargin = 10;
- (void)viewDidLoad
{
[super viewDidLoad];
// create a container view that all of our subviews for which we want to detect touches are:
CGFloat containerWidth = fmin(self.view.bounds.size.width, self.view.bounds.size.height) - kMargin * 2.0;
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(kMargin, kMargin, containerWidth, containerWidth)];
container.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:container];
// now create all of the subviews, specifying a tag for each; and
CGFloat cellWidth = (containerWidth - (4.0 * kMargin)) / 3.0;
for (NSInteger column = 0; column < 3; column++)
{
for (NSInteger row = 0; row < 3; row++)
{
UIView *cell = [[UIView alloc] initWithFrame:CGRectMake(kMargin + column * (cellWidth + kMargin),
kMargin + row * (cellWidth + kMargin),
cellWidth, cellWidth)];
cell.tag = row * 3 + column;
cell.backgroundColor = [UIColor lightGrayColor];
[container addSubview:cell];
}
}
// finally, create the gesture recognizer
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(handlePan:)];
[container addGestureRecognizer:pan];
}
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static NSMutableArray *gesturedSubviews;
// if we're starting a gesture, initialize our list of subviews that we've gone over
if (gesture.state == UIGestureRecognizerStateBegan)
{
gesturedSubviews = [NSMutableArray array];
}
// now figure out whether:
// (a) are we over a subview; and
// (b) is this a different subview than we last were over
CGPoint location = [gesture locationInView:gesture.view];
for (UIView *subview in gesture.view.subviews)
{
if (CGRectContainsPoint(subview.frame, location))
{
if (subview != [gesturedSubviews lastObject])
{
[gesturedSubviews addObject:subview];
// an example of the sort of graphical flourish to give the
// some visual cue that their going over the subview in question
// was recognized
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
subview.alpha = 0.5;
}
completion:^(BOOL finished){
subview.alpha = 1.0;
}];
}
}
}
// finally, when done, let's just log the subviews
// you would do whatever you would want here
if (gesture.state == UIGestureRecognizerStateEnded)
{
NSLog(@"We went over:");
for (UIView *subview in gesturedSubviews)
{
NSLog(@" %d", subview.tag);
}
// you might as well clean up your static variable when you're done
gesturedSubviews = nil;
}
}
显然,您可以以任何您想要的方式创建您的子视图,并以您想要的任何方式跟踪它们,但我们的想法是让子视图具有唯一的tag
数字,并且手势识别器只会看到您在一个手势中遍历它们的顺序.
即使我没有准确捕捉到您想要的内容,它至少向您展示了如何使用平移手势识别器来跟踪手指从一个子视图到另一个子视图的移动。
更新:
如果您想在用户登录时在屏幕上绘制路径,您可以CAShapeLayer
使用UIBezierPath
. 我将在下面演示这一点,但作为警告,我不得不指出这可能不是一个很好的安全功能:通常输入密码时,你会向用户展示足够多的内容,以便他们确认他们正在做他们想要什么,但还不够,以至于有人可以回头看看整个密码是什么。输入文本密码时,通常 iOS 会暂时显示您按下的最后一个键,但很快会将其转换为星号,这样您就无法看到整个密码。因此,我最初的建议。
但是,如果您真的一心想在用户绘制路径时向他们展示路径,您可以使用类似以下的内容。首先,这需要Quartz 2D。因此添加QuartzCore.framework
到您的项目中(请参阅链接到库或框架)。其次,导入 QuartCore 头文件:
#import <QuartzCore/QuartzCore.h>
第三,将 pan 处理程序替换为:
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static NSMutableArray *gesturedSubviews;
static UIBezierPath *path = nil;
static CAShapeLayer *shapeLayer = nil;
// if we're starting a gesture, initialize our list of subviews that we've gone over
if (gesture.state == UIGestureRecognizerStateBegan)
{
gesturedSubviews = [NSMutableArray array];
}
// now figure out whether:
// (a) are we over a subview; and
// (b) is this a different subview than we last were over
CGPoint location = [gesture locationInView:gesture.view];
for (UIView *subview in gesture.view.subviews)
{
if (!path)
{
// if the path hasn't be started, initialize it and the shape layer
path = [UIBezierPath bezierPath];
[path moveToPoint:location];
shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 2.0;
[gesture.view.layer addSublayer:shapeLayer];
}
else
{
// otherwise add this point to the layer's path
[path addLineToPoint:location];
shapeLayer.path = path.CGPath;
}
if (CGRectContainsPoint(subview.frame, location))
{
if (subview != [gesturedSubviews lastObject])
{
[gesturedSubviews addObject:subview];
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
subview.alpha = 0.5;
}
completion:^(BOOL finished){
subview.alpha = 1.0;
}];
}
}
}
// finally, when done, let's just log the subviews
// you would do whatever you would want here
if (gesture.state == UIGestureRecognizerStateEnded)
{
// assuming the tags are numbers between 0 and 9 (inclusive), we can build the password here
NSMutableString *password = [NSMutableString string];
for (UIView *subview in gesturedSubviews)
[password appendFormat:@"%c", subview.tag + 48];
NSLog(@"Password = %@", password);
// clean up our array of gesturedSubviews
gesturedSubviews = nil;
// clean up the drawing of the path on the screen the user drew
[shapeLayer removeFromSuperlayer];
shapeLayer = nil;
path = nil;
}
}
这会产生用户在手势进行时绘制的路径:
而不是显示用户在用户手指的每次移动时绘制的路径,也许您只是在子视图的中心之间绘制线条,例如:
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static NSMutableArray *gesturedSubviews;
static UIBezierPath *path = nil;
static CAShapeLayer *shapeLayer = nil;
// if we're starting a gesture, initialize our list of subviews that we've gone over
if (gesture.state == UIGestureRecognizerStateBegan)
{
gesturedSubviews = [NSMutableArray array];
}
// now figure out whether:
// (a) are we over a subview; and
// (b) is this a different subview than we last were over
CGPoint location = [gesture locationInView:gesture.view];
for (UIView *subview in gesture.view.subviews)
{
if (CGRectContainsPoint(subview.frame, location))
{
if (subview != [gesturedSubviews lastObject])
{
[gesturedSubviews addObject:subview];
if (!path)
{
// if the path hasn't be started, initialize it and the shape layer
path = [UIBezierPath bezierPath];
[path moveToPoint:subview.center];
shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 2.0;
[gesture.view.layer addSublayer:shapeLayer];
}
else
{
// otherwise add this point to the layer's path
[path addLineToPoint:subview.center];
shapeLayer.path = path.CGPath;
}
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
subview.alpha = 0.5;
}
completion:^(BOOL finished){
subview.alpha = 1.0;
}];
}
}
}
// finally, when done, let's just log the subviews
// you would do whatever you would want here
if (gesture.state == UIGestureRecognizerStateEnded)
{
// assuming the tags are numbers between 0 and 9 (inclusive), we can build the password here
NSMutableString *password = [NSMutableString string];
for (UIView *subview in gesturedSubviews)
[password appendFormat:@"%c", subview.tag + 48];
NSLog(@"Password = %@", password);
// clean up our array of gesturedSubviews
gesturedSubviews = nil;
// clean up the drawing of the path on the screen the user drew
[shapeLayer removeFromSuperlayer];
shapeLayer = nil;
path = nil;
}
}
这会产生类似的东西:
您有各种各样的选择,但希望您现在拥有构建块,以便您可以设计自己的解决方案。
原谅我 Rob,这里纯粹是抄袭 :) 在 swift 3.0 中需要相同的代码 :) 所以我把你写的这个很棒的小例子翻译成 swift 3.0。
ViewController.swift
import UIKit
class ViewController: UIViewController {
static let kMargin:CGFloat = 10.0;
override func viewDidLoad()
{
super.viewDidLoad()
// create a container view that all of our subviews for which we want to detect touches are:
let containerWidth = fmin(self.view.bounds.size.width, self.view.bounds.size.height) - ViewController.kMargin * 2.0
let container = UIView(frame: CGRect(x: ViewController.kMargin, y: ViewController.kMargin, width: containerWidth, height: containerWidth))
container.backgroundColor = UIColor.darkGray
view.addSubview(container)
// now create all of the subviews, specifying a tag for each; and
let cellWidth = (containerWidth - (4.0 * ViewController.kMargin)) / 3.0
for column in 0 ..< 3 {
for row in 0 ..< 3 {
let cell = UIView(frame: CGRect(x: ViewController.kMargin + CGFloat(column) * (cellWidth + ViewController.kMargin), y: ViewController.kMargin + CGFloat(row) * (cellWidth + ViewController.kMargin), width: cellWidth, height: cellWidth))
cell.tag = row * 3 + column;
container.addSubview(cell)
}
}
// finally, create the gesture recognizer
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
container.addGestureRecognizer(pan)
}
func handlePan(gesture: UIPanGestureRecognizer)
{
var gesturedSubviews : [UIView] = []
// if we're starting a gesture, initialize our list of subviews that we've gone over
if (gesture.state == .began)
{
gesturedSubviews.removeAll()
}
let location = gesture.location(in: gesture.view)
for subview in (gesture.view?.subviews)! {
if (subview.frame.contains(location)) {
if (subview != gesturedSubviews.last) {
gesturedSubviews.append(subview)
subview.backgroundColor = UIColor.blue
}
}
// finally, when done, let's just log the subviews
// you would do whatever you would want here
if (gesture.state != .recognized)
{
print("We went over:");
for subview in gesturedSubviews {
print(" %d", (subview as AnyObject).tag);
}
// you might as well clean up your static variable when you're done
}
}
}
}
更新:几乎就是这样;我也尝试翻译更新,但我的翻译遗漏了一些东西并且没有工作,所以我搜索了 SO 并制作了一个类似的,如果略有不同的最终解决方案。
import UIKit
import QuartzCore
class ViewController: UIViewController {
static let kMargin:CGFloat = 10.0;
override func viewDidLoad()
{
super.viewDidLoad()
// create a container view that all of our subviews for which we want to detect touches are:
let containerWidth = fmin(self.view.bounds.size.width, self.view.bounds.size.height) - ViewController.kMargin * 2.0
let container = UIView(frame: CGRect(x: ViewController.kMargin, y: ViewController.kMargin, width: containerWidth, height: containerWidth))
container.backgroundColor = UIColor.darkGray
view.addSubview(container)
// now create all of the subviews, specifying a tag for each; and
let cellWidth = (containerWidth - (4.0 * ViewController.kMargin)) / 3.0
for column in 0 ..< 3 {
for row in 0 ..< 3 {
let cell = UIView(frame: CGRect(x: ViewController.kMargin + CGFloat(column) * (cellWidth + ViewController.kMargin), y: ViewController.kMargin + CGFloat(row) * (cellWidth + ViewController.kMargin), width: cellWidth, height: cellWidth))
cell.tag = row * 3 + column;
container.addSubview(cell)
}
}
// finally, create the gesture recognizer
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
container.addGestureRecognizer(pan)
}
// Here's a Swift 3.0 version based on Rajesh Choudhary's answer:
func drawLine(onLayer layer: CALayer, fromPoint start: CGPoint, toPoint end:CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.fillColor = nil
line.lineWidth = 8
line.opacity = 0.5
line.strokeColor = UIColor.red.cgColor
layer.addSublayer(line)
}
var gesturedSubviews : [UIView] = []
var startX: CGFloat!
var startY: CGFloat!
var endX: CGFloat!
var endY: CGFloat!
func handlePan(gesture: UIPanGestureRecognizer) {
if (gesture.state == .began)
{
gesturedSubviews = [];
let location = gesture.location(in: gesture.view)
print("location \(location)")
startX = location.x
startY = location.y
}
if (gesture.state == .changed) {
let location = gesture.location(in: gesture.view)
print("location \(location)")
endX = location.x
endY = location.y
drawLine(onLayer: view.layer, fromPoint: CGPoint(x: startX, y: startY), toPoint: CGPoint(x:endX, y:endY))
startX = endX
startY = endY
}
if (gesture.state == .ended) {
let location = gesture.location(in: gesture.view)
print("location \(location)")
drawLine(onLayer: view.layer, fromPoint: CGPoint(x: startX, y: startY), toPoint: CGPoint(x:location.x, y:location.y))
}
// now figure out whether:
// (a) are we over a subview; and
// (b) is this a different subview than we last were over
let location = gesture.location(in: gesture.view)
print("location \(location)")
for subview in (gesture.view?.subviews)! {
if subview.frame.contains(location) {
if (subview != gesturedSubviews.last) {
gesturedSubviews.append(subview)
subview.backgroundColor = UIColor.blue
subview.alpha = 1.0
}
}
}
}
}