草庐IT

ios - 做手势时将 anchor 放在屏幕中央

coder 2023-07-16 原文

我有一个 ImageView ,它响应捏合、旋转和平移手势。我希望图像的收缩和旋转是相对于放置在屏幕中间的 anchor 完成的,就像使用 Xcode iPhone 模拟器通过按下选项键完成的那样。如果图像的中心可能被缩放和平移到不同的位置,我如何将 anchor 放置在屏幕中间?

这是我的缩放和旋转手势功能:

@IBAction func pinchGesture(_ gestureRecognizer: UIPinchGestureRecognizer) {
    // Move the achor point of the view's layer to the touch point
    // so that scaling the view and the layer becames simpler.
    self.adjustAnchorPoint(gestureRecognizer: gestureRecognizer)


    // Scale the view by the current scale factor.
    if(gestureRecognizer.state == .began) {
        // Reset the last scale, necessary if there are multiple objects with different scales
        lastScale = gestureRecognizer.scale
    }

    if (gestureRecognizer.state == .began || gestureRecognizer.state == .changed) {
        let currentScale = gestureRecognizer.view!.layer.value(forKeyPath:"transform.scale")! as! CGFloat
        // Constants to adjust the max/min values of zoom
        let kMaxScale:CGFloat = 15.0
        let kMinScale:CGFloat = 1.0
        var newScale = 1 -  (lastScale - gestureRecognizer.scale)
        newScale = min(newScale, kMaxScale / currentScale)
        newScale = max(newScale, kMinScale / currentScale)
        let transform = (gestureRecognizer.view?.transform)!.scaledBy(x: newScale, y: newScale);
        gestureRecognizer.view?.transform = transform
        lastScale = gestureRecognizer.scale  // Store the previous scale factor for the next pinch gesture call
        scale = currentScale // Save current scale for later use
    }
}

@IBAction func rotationGesture(_ gestureRecognizer: UIRotationGestureRecognizer) {
    // Move the achor point of the view's layer to the center of the
    // user's two fingers. This creates a more natural looking rotation.
    self.adjustAnchorPoint(gestureRecognizer: gestureRecognizer)

    // Apply the rotation to the view's transform.
    if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
        gestureRecognizer.view?.transform = (gestureRecognizer.view?.transform.rotated(by: gestureRecognizer.rotation))!
        // Set the rotation to 0 to avoid compouding the
        // rotation in the view's transform.
        angle += gestureRecognizer.rotation // Save rotation angle for later use
        gestureRecognizer.rotation = 0.0
    }
}

func adjustAnchorPoint(gestureRecognizer : UIGestureRecognizer) {
    if gestureRecognizer.state == .began {
        let view = gestureRecognizer.view
        let locationInView = gestureRecognizer.location(in: view)
        let locationInSuperview = gestureRecognizer.location(in: view?.superview)

        // Move the anchor point to the the touch point and change the position of the view
        view?.layer.anchorPoint = CGPoint(x: (locationInView.x / (view?.bounds.size.width)!), y: (locationInView.y / (view?.bounds.size.height)!))
        view?.center = locationInSuperview
    }
}

编辑

我发现人们并不急于参与其中。让我通过分享我在过去几天取得的一些进展来提供帮助。

首先,我编写了一个函数 centerAnchorPoint,它可以将图像的 anchor 正确地放置在屏幕的中心,而不管该 anchor 之前位于何处。但是,不得缩放或旋转图像才能正常工作。

func setAnchorPoint(_ anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x, y: view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x, y: view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

func centerAnchorPoint(gestureRecognizer : UIGestureRecognizer) {
    if gestureRecognizer.state == .ended {

        view?.layer.anchorPoint = CGPoint(x: (photo.bounds.midX / (view?.bounds.size.width)!), y: (photo.bounds.midY / (view?.bounds.size.height)!))
    }
}

func centerAnchorPoint() {

    // Position of the current anchor point
    let currentPosition = photo.layer.anchorPoint

    self.setAnchorPoint(CGPoint(x: 0.5, y: 0.5), forView: photo)
    // Center of the image
    let imageCenter = CGPoint(x: photo.center.x, y: photo.center.y)
    self.setAnchorPoint(currentPosition, forView: photo)

    // Center of the screen
    let screenCenter = CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY)

    // Distance between the centers
    let distanceX = screenCenter.x - imageCenter.x
    let distanceY = screenCenter.y - imageCenter.y

    // Find new anchor point
    let newAnchorPoint = CGPoint(x: (imageCenter.x+2*distanceX)/(UIScreen.main.bounds.size.width), y: (imageCenter.y+2*distanceY)/(UIScreen.main.bounds.size.height))

    //photo.layer.anchorPoint = newAnchorPoint
    self.setAnchorPoint(newAnchorPoint, forView: photo)

    let dotPath = UIBezierPath(ovalIn: CGRect(x: photo.layer.position.x-2.5, y: photo.layer.position.y-2.5, width: 5, height: 5))
    layer.path = dotPath.cgPath
}

函数 setAchorPoint 用于在不移动图像的情况下将 anchor 设置到新位置。

然后我更新了 panGesture 函数,在它的末尾插入:

if gestureRecognizer.state == .ended {
        self.centerAnchorPoint()
    }

编辑 2

好的,所以我将尝试简单地解释上面的代码。

我正在做的是:

  1. 找出照片中心与屏幕中心之间的距离
  2. 应用这个公式找到 anchor 的新位置:

newAnchorPointX = (imageCenter.x-distanceX)/screenWidth + distanceX/screenWidth

然后对 y 位置做同样的事情。

  1. 使用 setAnchorPoint 函数将此点设置为新的 anchor 而不移动照片

正如我所说,如果图像未缩放,这会很好用。如果是,则 anchor 不会停留在中心。

奇怪的是 distanceX 或 distanceY 并不完全取决于比例值,所以像这样的东西不太管用:

newAnchorPointX = (imageCenter.x-distanceX)/screenWidth + distanceX/(scaleValue*screenWidth)

编辑 3

我想出了缩放比例。看起来正确的比例因子必须是:

scaleFactor = photo.frame.size.width/photo.layer.bounds.size.width

我用它代替 scaleValue 并且效果非常好。

这样平移和缩放就完成了。剩下的就是轮换了,但看起来是最难的。

我首先想到的是将旋转矩阵应用于 X 和 Y 方向的增量,如下所示:

    let incrementX = (distanceX)/(screenWidth)
    let incrementY = (distanceY)/(screenHeight)

    // Applying rotation matrix
    let incX = incrementX*cos(angle)+incrementY*sin(angle)
    let incY = -incrementX*sin(angle)+incrementY*cos(angle)

    // Find new anchor point
    let newAnchorPoint = CGPoint(x: 0.5+incX, y: 0.5+incY)

但是这不起作用。

最佳答案

由于问题大部分在编辑中得到了回答,我不想重复太多。

大致上我对原始问题中发布的代码所做的更改:

  1. 删除了在捏合和旋转手势函数中对 adjustAnchorPoint 函数的调用。
  2. 将这段代码放在平移手势函数中,使 anchor 在平移照片后更新其位置:

    如果 gestureRecognizer.state == .ended { self.centerAnchorPoint()

  3. 更新了用于旋转的 centerAnchorPoint 函数。

一个完整的 centerAnchorPoint 函数(包括旋转):

    func centerAnchorPoint() {
        // Scale factor
        photo.transform = photo.transform.rotated(by: -angle)
        let curScale = photo.frame.size.width / photo.layer.bounds.size.width
        photo.transform = photo.transform.rotated(by: angle)

        // Position of the current anchor point
        let currentPosition = photo.layer.anchorPoint

        self.setAnchorPoint(CGPoint(x: 0.5, y: 0.5), forView: photo)
        // Center of the image
        let imageCenter = CGPoint(x: photo.center.x, y: photo.center.y)
        self.setAnchorPoint(currentPosition, forView: photo)

        // Center of the screen
        let screenCenter = CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY)

        // Distance between the centers
        let distanceX = screenCenter.x - imageCenter.x
        let distanceY = screenCenter.y - imageCenter.y

        // Apply rotational matrix to the distances
        let distX = distanceX*cos(angle)+distanceY*sin(angle)
        let distY = -distanceX*sin(angle)+distanceY*cos(angle)

        let incrementX = (distX)/(curScale*UIScreen.main.bounds.size.width)
        let incrementY = (distY)/(curScale*UIScreen.main.bounds.size.height)

        // Find new anchor point
        let newAnchorPoint = CGPoint(x: 0.5+incrementX, y: 0.5+incrementY)

        self.setAnchorPoint(newAnchorPoint, forView: photo)
}

这里要注意的关键是旋转矩阵必须应用于 distanceX 和 distanceY。比例因子也会更新以在整个旋转过程中保持不变。

关于ios - 做手势时将 anchor 放在屏幕中央,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45809994/

有关ios - 做手势时将 anchor 放在屏幕中央的更多相关文章

  1. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  2. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  3. 屏幕录制为什么没声音?检查这2项,轻松解决 - 2

    相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声

  4. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  5. python - 是否可以使用 Ruby 或 Python 禁用 anchor /引用来发出有效的 YAML? - 2

    是否可以在PyYAML或Ruby的Psych引擎中禁用创建anchor和引用(并有效地显式列出冗余数据)?也许我在网上搜索时遗漏了一些东西,但在Psych中似乎没有太多可用的选项,而且我也无法确定PyYAML是否允许这样做.基本原理是我必须序列化一些数据并将其以可读的形式传递给一个不是真正的技术同事进行手动验证。有些数据是多余的,但我需要以最明确的方式列出它们以提高可读性(anchor和引用是提高效率的好概念,但不是人类可读性)。Ruby和Python是我选择的工具,但如果有其他一些相当简单的方法来“展开”YAML文档,它可能就可以了。 最佳答案

  6. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  7. ruby - 为 IO::popen 拯救 "command not found" - 2

    当我将IO::popen与不存在的命令一起使用时,我在屏幕上打印了一条错误消息:irb>IO.popen"fakefake"#=>#irb>(irb):1:commandnotfound:fakefake有什么方法可以捕获此错误,以便我可以在脚本中进行检查? 最佳答案 是:升级到ruby​​1.9。如果您在1.9中运行它,则会引发Errno::ENOENT,您将能够拯救它。(编辑)这是在1.8中的一种hackish方式:error=IO.pipe$stderr.reopenerror[1]pipe=IO.popen'qwe'#

  8. ruby - IO::EAGAINWaitReadable:资源暂时不可用 - 读取会阻塞 - 2

    当我尝试使用“套接字”库中的方法“read_nonblock”时出现以下错误IO::EAGAINWaitReadable:Resourcetemporarilyunavailable-readwouldblock但是当我通过终端上的IRB尝试时它工作正常如何让它读取缓冲区? 最佳答案 IgetthefollowingerrorwhenItrytousethemethod"read_nonblock"fromthe"socket"library当缓冲区中的数据未准备好时,这是预期的行为。由于异常IO::EAGAINWaitReadab

  9. ruby - 如何使用 ruby​​ fibers 避免阻塞 IO - 2

    我需要将目录中的一堆文件上传到S3。由于上传所需的90%以上的时间都花在了等待http请求完成上,所以我想以某种方式同时执行其中的几个。Fibers能帮我解决这个问题吗?它们被描述为解决此类问题的一种方法,但我想不出在http调用阻塞时我可以做任何工作的任何方法。有什么方法可以在没有线程的情况下解决这个问题? 最佳答案 我没有使用1.9中的纤程,但是1.8.6中的常规线程可以解决这个问题。尝试使用队列http://ruby-doc.org/stdlib/libdoc/thread/rdoc/classes/Queue.html查看文

  10. ruby - 如何从 ruby​​ 中的 IO 对象获取文件名 - 2

    在ruby中...我有一个由外部进程创建的IO对象,我需要从中获取文件名。然而我似乎只能得到文件描述符(3),这对我来说不是很有用。有没有办法从此对象获取文件名甚至获取文件对象?我正在从通知程序中获取IO对象。所以这也可能是获取文件路径的一种方式? 最佳答案 关于howtogetathefilenameinC也有类似的问题,我将在这里以ruby​​的方式给出这个问题的答案。在Linux中获取文件名假设io是您的IO对象。以下代码为您提供了文件名。File.readlink("/proc/self/fd/#{io.fileno}")例

随机推荐