我正在尝试使用 ios 构建球形视图应用程序(与全景视图相同)。UITableView 中有列出的图像(paranonma)。您可以使用设备运动和手指手势以 360 度查看每个图像。但是,是否有可能在任何图像上的一个手势导致所有图像采取相同的手势效果?例如,如果我用手指循环顶部图像,则所有图像“也”都与顶部图像循环相同。“Bubbli”应用程序具有该功能。
我试图将 pangesturerecognizer 作为全局变量来共享手势,但它没有用。
我怎样才能..?
这是表格视图代码。
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
}
它是 tableviewcell_uiview 代码。
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)
}
}
它是定义 PanoramaView 类的 PanoramaView.swift。
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 {}
这是 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
)
}
}
如何集成所有单元格的手势管理器..?