改变UIImageView变换规模打破了移动系统

时间:2018-01-11 11:29:56

标签: ios swift uiimageview

我正在尝试创建一个可以在屏幕上移动和缩放的图像视图。问题是,当我改变图像的比例时,运动系统接缝会断开。

我编写了一些代码来从一个锚点拖动对象,这个锚点可能与UIImage的中心不同,但是比例破坏了这个过程。

 /*
See LICENSE folder for this sample’s licensing information.

Abstract:
Main view controller for the AR experience.
*/

import ARKit
import SceneKit
import UIKit
import ModelIO

class ViewController: UIViewController, ARSessionDelegate , UIGestureRecognizerDelegate{



// MARK: Outlets

@IBOutlet var sceneView: ARSCNView!

@IBOutlet weak var blurView: UIVisualEffectView!


@IBOutlet weak var dropdown: UIPickerView!
@IBOutlet weak var AddStickerButton: UIButton!
@IBOutlet weak var deleteStickerButton: UIImageView!
var offset : CGPoint = CGPoint.zero
var isDeleteVisible : Bool = false


let array:[String] = ["HappyHeart_Lisa", "Logo_bucato", "Sweety_2_Lisa", "Sweety_Lisa", "Tonglue_Lisa"]


lazy var statusViewController: StatusViewController = {
    return childViewControllers.lazy.flatMap({ $0 as? StatusViewController }).first!
}()

var stickers = [Sticker]()


// MARK: Properties

var myScene : SCNScene!
/// Convenience accessor for the session owned by ARSCNView.
var session: ARSession {
    sceneView.session.configuration
    //sceneView.scene.background.contents = UIColor.black

    return sceneView.session
}



var nodeForContentType = [VirtualContentType: VirtualFaceNode]()    //Tiene sotto controllo la selezione(Tipo maschera)

let contentUpdater = VirtualContentUpdater()                    //Chiama la VirtualContentUpdater.swift

var selectedVirtualContent: VirtualContentType = .faceGeometry {
    didSet {
        // Set the selected content based on the content type.
        contentUpdater.virtualFaceNode = nodeForContentType[selectedVirtualContent]
    }
}

// MARK: - View Controller Life Cycle

override func viewDidLoad() {
    super.viewDidLoad()

    sceneView.delegate = contentUpdater
    sceneView.session.delegate = self
    sceneView.automaticallyUpdatesLighting = true

    createFaceGeometry()

    // Set the initial face content, if any.
    contentUpdater.virtualFaceNode = nodeForContentType[selectedVirtualContent]

    // Hook up status view controller callback(s).
    statusViewController.restartExperienceHandler = { [unowned self] in
        self.restartExperience()


    }

    let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(scale))
    let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotate))
    pinchGesture.delegate = self
    rotationGesture.delegate = self
    view.addGestureRecognizer(pinchGesture)
    view.addGestureRecognizer(rotationGesture)



}



override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    /*
        AR experiences typically involve moving the device without
        touch input for some time, so prevent auto screen dimming.
    */
    UIApplication.shared.isIdleTimerDisabled = true

    resetTracking()
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    session.pause()
}

// MARK: - Setup

/// - Tag: CreateARSCNFaceGeometry
func createFaceGeometry() {
    // This relies on the earlier check of `ARFaceTrackingConfiguration.isSupported`.
    let device = sceneView.device!
    let maskGeometry = ARSCNFaceGeometry(device: device)!
    let glassesGeometry = ARSCNFaceGeometry(device: device)!

    nodeForContentType = [
        .faceGeometry: Mask(geometry: maskGeometry),
        .overlayModel: GlassesOverlay(geometry: glassesGeometry),
        .blendShapeModel: RobotHead(),
        .sfere: RobotHead()
    ]
}

// MARK: - ARSessionDelegate

func session(_ session: ARSession, didFailWithError error: Error) {
    guard error is ARError else { return }

    let errorWithInfo = error as NSError
    let messages = [
        errorWithInfo.localizedDescription,
        errorWithInfo.localizedFailureReason,
        errorWithInfo.localizedRecoverySuggestion
    ]
    let errorMessage = messages.flatMap({ $0 }).joined(separator: "\n")

    DispatchQueue.main.async {
        self.displayErrorMessage(title: "The AR session failed.", message: errorMessage)
    }
}

func sessionWasInterrupted(_ session: ARSession) {
    blurView.isHidden = false
    statusViewController.showMessage("""
    SESSION INTERRUPTED
    The session will be reset after the interruption has ended.
    """, autoHide: false)
}

func sessionInterruptionEnded(_ session: ARSession) {
    blurView.isHidden = true

    DispatchQueue.main.async {
        self.resetTracking()
    }
}

/// - Tag: ARFaceTrackingSetup
func resetTracking() {
    statusViewController.showMessage("STARTING A NEW SESSION")

    guard ARFaceTrackingConfiguration.isSupported else { return }
    let configuration = ARFaceTrackingConfiguration()
    configuration.isLightEstimationEnabled = true

    session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
}

// MARK: - Interface Actions

/// - Tag: restartExperience
func restartExperience() {
    // Disable Restart button for a while in order to give the session enough time to restart.
    statusViewController.isRestartExperienceButtonEnabled = false
    DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
        self.statusViewController.isRestartExperienceButtonEnabled = true
    }

    resetTracking()
}

// MARK: - Error handling

func displayErrorMessage(title: String, message: String) {
    // Blur the background.
    blurView.isHidden = false

    // Present an alert informing about the error that has occurred.
    let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
    let restartAction = UIAlertAction(title: "Restart Session", style: .default) { _ in
        alertController.dismiss(animated: true, completion: nil)
        self.blurView.isHidden = true
        self.resetTracking()
    }
    alertController.addAction(restartAction)
    present(alertController, animated: true, completion: nil)
}



//Create a new Sticker


func createNewSticker(){
    stickers.append(Sticker(view : self.view, viewCtrl : self))

}


@IBAction func addNewSticker(_ sender: Any) {
    createNewSticker()
}

//Function To Move the Stickers, all the Touch Events Listener

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for touch in (touches as! Set<UITouch>) {
        var location = touch.location(in: self.view)
        for sticker in stickers {
            if(sticker.imageView.frame.contains(location) && !isSomeOneMoving()){
                //sticker.imageView.center = location

                offset = touch.location(in: sticker.imageView)

                let offsetPercentage = CGPoint(x: offset.x / sticker.imageView.bounds.width, y: offset.y / sticker.imageView.bounds.height)

                let offsetScaled = CGPoint(x: sticker.imageView.frame.width * offsetPercentage.x, y: sticker.imageView.frame.height * offsetPercentage.y)

                offset.x = (sticker.imageView.frame.width / 2) - offsetScaled.x

                offset.y = (sticker.imageView.frame.height / 2) - offsetScaled.y

                location = touch.location(in: self.view)
                location.x = (location.x + offset.x)
                location.y = (location.y + offset.y)

                sticker.imageView.center = location

                disableAllStickersMovements()
                isDeleteVisible = true
                sticker.isStickerMoving = true;
                deleteStickerButton.isHidden = false

            }
        }
    }
}

func disableAllStickersMovements(){
    for sticker in stickers {
        sticker.isStickerMoving = false;
    }
}

func isSomeOneMoving() -> Bool{
    for sticker in stickers {
        if(sticker.isStickerMoving){
            return true
        }
    }
    return false
}

var lastLocationTouched : CGPoint = CGPoint.zero
var lastStickerTouched : Sticker = Sticker()



override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

    for touch in (touches as! Set<UITouch>) {
        var location = touch.location(in: self.view)
        for sticker in stickers {
            if(sticker.imageView.frame.contains(location) && sticker.isStickerMoving){
                lastLocationTouched = location

                location = touch.location(in: self.view)
                location.x = (location.x + offset.x)
                location.y = (location.y + offset.y)

                sticker.imageView.center = location
                //sticker.imageView.center = location
            }
            if(deleteStickerButton.frame.contains(lastLocationTouched) && isDeleteVisible && sticker.isStickerMoving){
                sticker.imageView.alpha = CGFloat(0.5)
            }else{
                sticker.imageView.alpha = CGFloat(1)
            }
        }
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    for sticker in stickers {

        if(deleteStickerButton.frame.contains(lastLocationTouched) && isDeleteVisible && sticker.isStickerMoving){
            removeASticker(sticker : sticker)
            disableAllStickersMovements()

        }
    }
    disableAllStickersMovements()

    isDeleteVisible = false
    deleteStickerButton.isHidden = true
}
func removeASticker(sticker : Sticker  ){
    sticker.imageView.removeFromSuperview()
    let stickerPosition = stickers.index(of: sticker)!
    stickers.remove(at: stickerPosition)
    for sticker in stickers {
        sticker.isStickerMoving = false;
    }
}

var identity = CGAffineTransform.identity


@objc func scale(_ gesture: UIPinchGestureRecognizer) {
    for sticker in stickers {
        if(sticker.isStickerMoving){
            switch gesture.state {
            case .began:
                identity = sticker.imageView.transform
            case .changed,.ended:
                sticker.imageView.transform = identity.scaledBy(x: gesture.scale, y: gesture.scale)
            case .cancelled:
                break
            default:
                break
            }
        }
    }

}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}
@objc func rotate(_ gesture: UIRotationGestureRecognizer) {
    for sticker in stickers {
        if(sticker.isStickerMoving){
            sticker.imageView.transform = sticker.imageView.transform.rotated(by: gesture.rotation)
        }
    }
}

}

然后是贴纸类

import UIKit
import Foundation

class Sticker : NSObject, UIGestureRecognizerDelegate{



var location = CGPoint(x: 0 , y: 0);

var sticker_isMoving = false;

let imageView = UIImageView()

var isStickerMoving : Bool = false;

init(view : UIView, viewCtrl : ViewController ) {
    super.init()
    imageView.image = UIImage(named: "BroccolFace_Lisa.png")
    imageView.isUserInteractionEnabled = true
    imageView.contentMode = UIViewContentMode.scaleAspectFit

    imageView.frame = CGRect(x: view.center.x, y: view.center.y, width: 200, height: 200)
    view.addSubview(imageView)


}

override init(){

}

}

2 个答案:

答案 0 :(得分:2)

这是因为imageView.boundstouch.location(in: imageView)处于未缩放的值中。这将克服这个问题:

offset = touch.location(in: imageView)

let offsetPercentage = CGPoint(x: offset.x / imageView.bounds.width, y: offset.y / imageView.bounds.height)

let offsetScaled = CGPoint(x: imageView.frame.width * offsetPercentage.x, y: imageView.frame.height * offsetPercentage.y)

offset.x = (imageView.frame.width / 2) - offsetScaled.x

offset.y = (imageView.frame.height / 2) - offsetScaled.y

基本上,它会根据未缩放的值将偏移转换为百分比,然后根据imageView框架(由比例修改)将其转换为缩放值。然后使用它来计算偏移量。

编辑(第二位)

这是更完整的方法,它可以解决因缩放或旋转而可能出现的任何问题。

  1. 添加此结构以保存图像拖动的详细信息:

    struct DragInfo {
        let imageView: UIImageView
        let startPoint: CGPoint
    }
    
  2. 添加这些实例变量(如果需要,还可以删除偏移量):

    var dragStartPoint: CGPoint = CGPoint.zero
    var currentDragItems: [DragInfo] = []
    var dragTouch: UITouch?
    
  3. 更改touchesBegan到此:

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard self.dragTouch == nil, let touch = touches.first else { return }
    
        self.dragTouch = touch
    
        let location = touch.location(in: self.view)
    
        self.dragStartPoint = location
    
        for imageView in self.imageList {
            if imageView.frame.contains(location) {
                self.currentDragItems.append(DragInfo(imageView: imageView, startPoint: imageView.center))
            }
        }
    }
    
  4. 更改touchesMoved到此:

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let dragTouch = self.dragTouch else { return }
    
        for touch in touches {
            if touch == dragTouch {
                let location = touch.location(in: self.view)
                let offset = CGPoint(x: location.x - self.dragStartPoint.x, y: location.y - self.dragStartPoint.y)
                for dragInfo in self.currentDragItems {
                    let imageOffSet = CGPoint(x: dragInfo.startPoint.x + offset.x, y: dragInfo.startPoint.y + offset.y)
                    dragInfo.imageView.center = imageOffSet
                }
            }
        }
    }
    
  5. 更改touchesEnded到此:

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let dragTouch = self.dragTouch, touches.contains(dragTouch) else { return }
    
        self.currentDragItems.removeAll()
        self.dragTouch = nil
    }
    
  6. 在使用的手势识别器上设置以下属性:

    scaleGesture.delaysTouchesEnded = false
    scaleGesture.cancelsTouchesInView = false
    
    rotationGesture.delaysTouchesEnded = false
    rotationGesture.cancelsTouchesInView = false
    
  7. 关于它是如何工作的一些解释。

    对于所有触摸事件,它只考虑第一次触摸,因为从多次触摸拖动没有多大意义(如果两次触摸在同一图像视图上并且移动方式不同则会怎样)。它记录了这种触摸,然后只考虑用于拖动物体的触摸。

    当调用touchesBegan时,它会检查没有触摸是否存在拖动(表示正在进行拖动)并且它会找到触摸下的所有图像视图,并且每个图像视图都会记录其自身的详细信息并在DragInfo中开始中心位置结构并将其存储在currentDragItems数组中。它还记录触摸在主视图中开始的位置以及启动它的触摸。

    当调用touchesMoved时,它仅考虑开始拖动的触摸,并计算触摸在主视图中开始的原始位置的偏移,然后沿着拖动中涉及的图像列表向下计算并计算其新的基于中心的图像在原始起始位置和计算的偏移量上,将其设置为新的中心。

    当调用touchesEnded时,假设它是结束的拖动触摸,它会清除DragInfo对象的数组,为下一次拖动做好准备。

    您需要在所有手势识别器上设置delaysTouchesEnded和cancelsTouchesInView属性,以便所有触摸都传递到视图,否则特别是touchesEnded方法不会被调用。

    进行这样的计算可以消除缩放和旋转的问题,因为您只关心初始位置的偏移。如果在分别保存细节的同时拖动多个图像视图,它也可以工作。

    现在有一些事情需要注意:

    1. 您需要输入应用所需的所有其他代码,因为这只是展示这个想法的基本示例。
    2. 这假设您只想拖动您在开始时拾取的图像视图。如果你想在拖动时收集图像视图,则需要开发一个更复杂的系统。
    3. 正如我所说,一次只能进行一次拖动操作,并且第一次触摸被注册为此源触摸。然后使用该源触摸来过滤掉可能发生的任何其他触摸。这样做是为了保持简单,否则你将不得不考虑各种奇怪的情况,比如同一个图像视图上是否有多个触摸。
    4. 我希望这一切都有意义,你可以调整它来解决你的问题。

答案 1 :(得分:1)

这是一个扩展程序,用于使用UIPanGestureRecognizerUIPinchGestureRecognizerUIRotationGestureRecognizer

平移,捏合和旋转图像
extension ViewController : UIGestureRecognizerDelegate {

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

func panGesture(gesture: UIPanGestureRecognizer) {
    switch gesture.state {
    case .ended: fallthrough
    case .changed:
        let translation = gesture.translation(in: gesture.view)

        if let view = gesture.view {
            var finalPoint = CGPoint(x:view.center.x + translation.x, y:view.center.y + translation.y)

            finalPoint.x = min(max(finalPoint.x, 0), self.myImageView.bounds.size.width)
            finalPoint.y = min(max(finalPoint.y, 0), self.myImageView.bounds.size.height)

            view.center = finalPoint

            gesture.setTranslation(CGPoint.zero, in: gesture.view)
        }

    default : break
    }
}

func pinchGesture(gesture: UIPinchGestureRecognizer) {
    switch gesture.state {
    case .changed:
        let scale = gesture.scale
        gesture.view?.transform = gesture.view!.transform.scaledBy(x: scale, y: scale)
        gesture.scale = 1
    default : break
    }
}

func rotateGesture(gesture: UIRotationGestureRecognizer) {
    switch gesture.state {
    case .changed:
        let rotation = gesture.rotation
        gesture.view?.transform = gesture.view!.transform.rotated(by: rotation)
        gesture.rotation = 0
    default : break
    }
}
}

设置UIGestureRecognizerDelegate将有助于您同时执行三种手势。