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..?