0

I am trying to construct shperical view application(same with panorama view) with ios. there are listed images(paranonma) in UITableView. you can view each image as 360 degrees using both device motion and finger gesture. But, is it possible that one gesture at any image leads all image to take same effect of the gesture? for example, if I circulate top image by finger, all image "also" circulates same with the top image. "Bubbli" application has that function.

I tried to put pangesturerecognizer as global variable to shared gesture, but It didn't work.

How can i..?

it's tableview code.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "pic_cell", for: indexPath) as! pic_TableViewCell
    let tmp = tmp_list[indexPath.row]
    cell.pic_View.loadPanoramaView(image: tmp)
    return cell
}

it's tableviewcell_uiview code.

class TableViewCell_UIView: UIView {

var image_name:String = ""

lazy var device: MTLDevice = {
    guard let device = MTLCreateSystemDefaultDevice() else {
        fatalError("Failed to create MTLDevice")
    }
    return device
}()

weak var panoramaView: PanoramaView?

func loadPanoramaView(image: String) {
    #if arch(arm) || arch(arm64)
        let panoramaView = PanoramaView(frame: view.bounds, device: device)
    #else
        let panoramaView = PanoramaView(frame: self.bounds) // iOS Simulator
    #endif
    panoramaView.setNeedsResetRotation()
    panoramaView.translatesAutoresizingMaskIntoConstraints = false
    self.addSubview(panoramaView)

    // fill parent view
    let constraints: [NSLayoutConstraint] = [
        panoramaView.topAnchor.constraint(equalTo: self.topAnchor),
        panoramaView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
        panoramaView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
        panoramaView.trailingAnchor.constraint(equalTo: self.trailingAnchor)
    ]
    NSLayoutConstraint.activate(constraints)

    // double tap to reset rotation
    let doubleTapGestureRecognizer = UITapGestureRecognizer(target: panoramaView, action: #selector(PanoramaView.setNeedsResetRotation(_:)))

    doubleTapGestureRecognizer.numberOfTapsRequired = 2
    panoramaView.addGestureRecognizer(doubleTapGestureRecognizer)

    self.panoramaView = panoramaView

    panoramaView.load(UIImage(named: image)!, format: .mono)
}


}

it's PanoramaView.swift which defines PanoramaView class.

final class PanoramaView: UIView, SceneLoadable {
#if (arch(arm) || arch(arm64)) && os(iOS)
public let device: MTLDevice
#endif

public var scene: SCNScene? {
    get {
        return scnView.scene
    }
    set(value) {
        orientationNode.removeFromParentNode()
        value?.rootNode.addChildNode(orientationNode)
        scnView.scene = value
    }
}

public weak var sceneRendererDelegate: SCNSceneRendererDelegate?



public lazy var orientationNode: OrientationNode = {
    let node = OrientationNode()
    let mask = CategoryBitMask.all.subtracting(.rightEye)
    node.pointOfView.camera?.categoryBitMask = mask.rawValue
    return node
}()



lazy var scnView: SCNView = {
    #if (arch(arm) || arch(arm64)) && os(iOS)
        let view = SCNView(frame: self.bounds, options: [
            SCNView.Option.preferredRenderingAPI.rawValue: SCNRenderingAPI.metal.rawValue,
            SCNView.Option.preferredDevice.rawValue: self.device
            ])
    #else
        let view = SCNView(frame: self.bounds)
    #endif
    view.backgroundColor = .black
    view.isUserInteractionEnabled = false
    view.delegate = self
    view.pointOfView = self.orientationNode.pointOfView
    view.isPlaying = true
    self.addSubview(view)
    return view
}()


// to integrated panGesture


fileprivate lazy var panGestureManager: PanoramaPanGestureManager = {
    let manager = PanoramaPanGestureManager(rotationNode: self.orientationNode.userRotationNode)
    manager.minimumVerticalRotationAngle = -60 / 180 * .pi
    manager.maximumVerticalRotationAngle = 60 / 180 * .pi
    return manager
}()



fileprivate lazy var interfaceOrientationUpdater: InterfaceOrientationUpdater = {
    return InterfaceOrientationUpdater(orientationNode: self.orientationNode)
}()



#if (arch(arm) || arch(arm64)) && os(iOS)
public init(frame: CGRect, device: MTLDevice) {
self.device = device
super.init(frame: frame)
addGestureRecognizer(panGestureManager.gestureRecognizer) // modify
//addGestureRecognizer(setGestureRecognizer())
}
#else
public override init(frame: CGRect) {
    super.init(frame: frame)
    addGestureRecognizer(panGestureManager.gestureRecognizer) // modify
    //addGestureRecognizer(setGestureRecognizer())
}
#endif


public required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

deinit {
    orientationNode.removeFromParentNode()
}

public override func layoutSubviews() {
    super.layoutSubviews()

    scnView.frame = bounds
}

public override func willMove(toWindow newWindow: UIWindow?) {
    if newWindow == nil {
        interfaceOrientationUpdater.stopAutomaticInterfaceOrientationUpdates()
    } else {
        interfaceOrientationUpdater.startAutomaticInterfaceOrientationUpdates()
        interfaceOrientationUpdater.updateInterfaceOrientation()
    }
}
}

extension PanoramaView: ImageLoadable {}

it's PanoramaPanGestureManager.swift

final class PanoramaPanGestureManager {
let rotationNode: SCNNode

var allowsVerticalRotation = true
var minimumVerticalRotationAngle: Float?
var maximumVerticalRotationAngle: Float?

var allowsHorizontalRotation = true
var minimumHorizontalRotationAngle: Float?
var maximumHorizontalRotationAngle: Float?

lazy var gestureRecognizer: UIPanGestureRecognizer = {
    let recognizer = AdvancedPanGestureRecognizer()
    recognizer.addTarget(self, action: #selector(handlePanGesture(_:)))
    recognizer.earlyTouchEventHandler = { [weak self] in
        self?.stopAnimations()
        self?.resetReferenceAngles()
    }
    return recognizer
}()

private var referenceAngles: SCNVector3?

init(rotationNode: SCNNode) {
    self.rotationNode = rotationNode
}

@objc func handlePanGesture(_ sender: UIPanGestureRecognizer) {
    guard let view = sender.view else {
        return
    }

    switch sender.state {
    case .changed:
        guard let referenceAngles = referenceAngles else {
            break
        }

        var angles = SCNVector3Zero
        let viewSize = max(view.bounds.width, view.bounds.height)
        let translation = sender.translation(in: view)

        if allowsVerticalRotation {
            var angle = referenceAngles.x + Float(translation.y / viewSize) * (.pi / 2)
            if let minimum = minimumVerticalRotationAngle {
                angle = max(angle, minimum)
            }
            if let maximum = maximumVerticalRotationAngle {
                angle = min(angle, maximum)
            }
            angles.x = angle
        }

        if allowsHorizontalRotation {
            var angle = referenceAngles.y + Float(translation.x / viewSize) * (.pi / 2)
            if let minimum = minimumHorizontalRotationAngle {
                angle = max(angle, minimum)
            }
            if let maximum = maximumHorizontalRotationAngle {
                angle = min(angle, maximum)
            }
            angles.y = angle
        }

        SCNTransaction.lock()
        SCNTransaction.begin()
        SCNTransaction.disableActions = true

        rotationNode.eulerAngles = angles.normalized

        SCNTransaction.commit()
        SCNTransaction.unlock()

    case .ended:
        var angles = rotationNode.eulerAngles
        let velocity = sender.velocity(in: view)
        let viewSize = max(view.bounds.width, view.bounds.height)

        if allowsVerticalRotation {
            var angle = angles.x
            angle += Float(velocity.y / viewSize) / .pi
            if let minimum = minimumVerticalRotationAngle {
                angle = max(angle, minimum)
            }
            if let maximum = maximumVerticalRotationAngle {
                angle = min(angle, maximum)
            }
            angles.x = angle
        }

        if allowsHorizontalRotation {
            var angle = angles.y
            angle += Float(velocity.x / viewSize) / .pi
            if let minimum = minimumHorizontalRotationAngle {
                angle = max(angle, minimum)
            }
            if let maximum = maximumHorizontalRotationAngle {
                angle = min(angle, maximum)
            }
            angles.y = angle
        }

        SCNTransaction.lock()
        SCNTransaction.begin()
        SCNTransaction.animationDuration = 1
        SCNTransaction.animationTimingFunction = CAMediaTimingFunction(controlPoints: 0.165, 0.84, 0.44, 1)

        rotationNode.eulerAngles = angles

        SCNTransaction.commit()
        SCNTransaction.unlock()

    default:
        break
    }
}

func stopAnimations() {
    SCNTransaction.lock()
    SCNTransaction.begin()
    SCNTransaction.disableActions = true

    rotationNode.eulerAngles = rotationNode.presentation.eulerAngles.normalized
    rotationNode.removeAllAnimations()

    SCNTransaction.commit()
    SCNTransaction.unlock()
}

private func resetReferenceAngles() {
    referenceAngles = rotationNode.presentation.eulerAngles
}
}

private final class AdvancedPanGestureRecognizer: UIPanGestureRecognizer {
var earlyTouchEventHandler: (() -> Void)?

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
    super.touchesBegan(touches, with: event)

    if state == .possible {
        earlyTouchEventHandler?()
    }
}
}

private extension Float {
var normalized: Float {
    let angle: Float = self

    let π: Float = .pi
    let π2: Float = π * 2

    if angle > π {
        return angle - π2 * ceil(abs(angle) / π2)
    } else if angle < -π {
        return angle + π2 * ceil(abs(angle) / π2)
    } else {
        return angle
    }
}
}

private extension SCNVector3 {
var normalized: SCNVector3 {
    let angles: SCNVector3 = self

    return SCNVector3(
        x: angles.x.normalized,
        y: angles.y.normalized,
        z: angles.z.normalized
    )
}
}

how can I integrate all cell's gesturemanager..?

loveme
  • 1
  • 1
  • I can't test right now, but when you are creating your cells could you add the gesture to it? So inside your tableView cellForRowAtIndex apply the same gesture, and have the function the gesture calls pan on each of the cells that are visible in the table. –  Jul 12 '17 at 20:08
  • yes, each cell has PanoramaView(class) which inherits UIView. In my original code, In each PanoramaView class, gesturemanager is created and addGestureRecognizer is also created. – loveme Jul 12 '17 at 20:13
  • How can I apply the same gesture at each cell..? I tried to put one global gestureRecognizer and make each cell call addGestureRecognizer(globalGestureRecognizer). but it didn't work.. – loveme Jul 12 '17 at 20:16
  • In a word yes. But its hacky. GestureRecognisers only has one view property. Have a look at this thread, someone has a workaround: https://stackoverflow.com/questions/4747238/can-you-attach-a-uigesturerecognizer-to-multiple-views/7883902#7883902 –  Jul 12 '17 at 20:20
  • Yes, it's quite possible. "It didn't work" is profoundly un-helpful. You need to post the relevant code, along with detailed information about what's going wrong. – Duncan C Jul 12 '17 at 20:21
  • Thank you for reply. I edited and added some code related to my questions. Could you give me some directions how to...? – loveme Jul 12 '17 at 20:46
  • I just solved it! OrientationNode dealing with following gesture has to be global! everybody thanks. – loveme Jul 12 '17 at 21:17
  • If the problem was solved please post the solution as an answer or delete the question. – lukess Jul 13 '17 at 02:28

0 Answers0