我正在尝试创建一个可以在屏幕上移动和缩放的图像视图。问题是,当我改变图像的比例时,运动系统接缝会断开。
我编写了一些代码来从一个锚点拖动对象,这个锚点可能与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(){
}
}
答案 0 :(得分:2)
这是因为imageView.bounds
和touch.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框架(由比例修改)将其转换为缩放值。然后使用它来计算偏移量。
编辑(第二位)
这是更完整的方法,它可以解决因缩放或旋转而可能出现的任何问题。
添加此结构以保存图像拖动的详细信息:
struct DragInfo {
let imageView: UIImageView
let startPoint: CGPoint
}
添加这些实例变量(如果需要,还可以删除偏移量):
var dragStartPoint: CGPoint = CGPoint.zero
var currentDragItems: [DragInfo] = []
var dragTouch: UITouch?
更改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))
}
}
}
更改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
}
}
}
}
更改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
}
在使用的手势识别器上设置以下属性:
scaleGesture.delaysTouchesEnded = false
scaleGesture.cancelsTouchesInView = false
rotationGesture.delaysTouchesEnded = false
rotationGesture.cancelsTouchesInView = false
关于它是如何工作的一些解释。
对于所有触摸事件,它只考虑第一次触摸,因为从多次触摸拖动没有多大意义(如果两次触摸在同一图像视图上并且移动方式不同则会怎样)。它记录了这种触摸,然后只考虑用于拖动物体的触摸。
当调用touchesBegan时,它会检查没有触摸是否存在拖动(表示正在进行拖动)并且它会找到触摸下的所有图像视图,并且每个图像视图都会记录其自身的详细信息并在DragInfo中开始中心位置结构并将其存储在currentDragItems数组中。它还记录触摸在主视图中开始的位置以及启动它的触摸。
当调用touchesMoved时,它仅考虑开始拖动的触摸,并计算触摸在主视图中开始的原始位置的偏移,然后沿着拖动中涉及的图像列表向下计算并计算其新的基于中心的图像在原始起始位置和计算的偏移量上,将其设置为新的中心。
当调用touchesEnded时,假设它是结束的拖动触摸,它会清除DragInfo对象的数组,为下一次拖动做好准备。
您需要在所有手势识别器上设置delaysTouchesEnded和cancelsTouchesInView属性,以便所有触摸都传递到视图,否则特别是touchesEnded方法不会被调用。
进行这样的计算可以消除缩放和旋转的问题,因为您只关心初始位置的偏移。如果在分别保存细节的同时拖动多个图像视图,它也可以工作。
现在有一些事情需要注意:
我希望这一切都有意义,你可以调整它来解决你的问题。
答案 1 :(得分:1)
这是一个扩展程序,用于使用UIPanGestureRecognizer
,UIPinchGestureRecognizer
和UIRotationGestureRecognizer
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
将有助于您同时执行三种手势。