【问题标题】:How can I "Fold" a UIView in 3D along a line?如何沿一条线“折叠” 3D UIView?
【发布时间】:2020-05-05 09:33:59
【问题描述】:

如果你有一个 UIView

想象一下视图充满了文本、动画、猫照片、渐变,或者实际上是 UIView 可以充满的任何东西。

在 3D 中很容易将其向左倾斜

或回到右边..

(顺便说一句,我强烈推荐完美的OHCubeViewohcubeview,当你必须做那种事情时。)

但我想沿接缝创建一个视图,并让一侧向左后退,另一侧向后退向右..

我的第一个解决方案很简单:无论视图是什么(用户正在输入文本、猫的照片等等),只需完美地复制它,然后以显而易见的方式解决问题。

这很不雅,但确实有效。

但是……

我想像这样折叠的东西是...一个 SKView 里面有一个发射器

粒子发射器当然是随机的,虽然两个相同的发射器看起来很相似,但这并不是真正的解决方案。

基本上,如果有人知道如何将 UIView“复制”到另一个、嗯、视图或其他东西......

问题会解决的。

例如,对于游戏引擎中的此类问题,您只需将“虚拟相机”(无论您最喜欢的游戏引擎中的概念是什么)放在有问题的事物上,然后将另一半或其他任何东西放在任何地方你想要,但是,我不知道在 iOS 中的类比!

有人对此有意见吗:

【问题讨论】:

  • 不完全是你想要的,但你也许可以从这个developer.apple.com/documentation/quartzcore/careplicatorlayer中破解一些东西
  • 这不是您可以与 CADisplayLink 一起使用的东西吗?即一个页面是真正的渲染,第二个页面只是在每个屏幕刷新率下复制原始页面的快照。
  • @DanielGalasko ,你真的应该把它当作任何答案,否则赏金将被浪费
  • 真正令人着迷的事情,@Aris(也许应该将其作为任何形式的答案,因为否则赏金将被浪费)

标签: ios swift uiview core-graphics skview


【解决方案1】:

我可以想到两种方法。

1) 弯道法

您可以使用 SKWarpGeometry 弯曲您的场景。从语义上讲,您会在游戏场景中设想一个 2 x 2 象限网格,由 3 x 3 点数据结构描述。你会稍微移动左右边缘,并使它们更小。而且你会让中心垂直边缘更长一点。

Apple 的文档还提到,基本上任何类型的节点(包括 SKEmitterNode)都可以通过将其放入 SKEffectNode 并对其应用扭曲来进行扭曲。见:https://developer.apple.com/documentation/spritekit/skwarpgeometrygrid/animate_the_warping_of_a_sprite?language=objc

我们所要做的就是让一个效果节点成为一切的根节点,并对其应用扭曲。现在,这行得通,但只要其中有一个发射器,经线就会开始像癫痫一样抽搐。我认为这是一个错误。无论如何,我都会包含代码,以防它稍后修复,因为它应该根据文档工作。

let sourcePoints = [
    // bottom row: left, center, right
    vector_float2(0.0, 0.0),
    vector_float2(0.5, 0.0),
    vector_float2(1.0, 0.0),

    // middle row: left, center, right
    vector_float2(0.0, 0.5),
    vector_float2(0.5, 0.5),
    vector_float2(1.0, 0.5),

    // top row: left, center, right
    vector_float2(0.0, 1.0),
    vector_float2(0.5, 1.0),
    vector_float2(1.0, 1.0)
]

var destinationPoints = sourcePoints

// Bring lefts in, and make the edge shorter
destinationPoints[0] = vector_float2(0.1, 0.1)
destinationPoints[3] = vector_float2(0.1, 0.4)
destinationPoints[6] = vector_float2(0.1, 0.9)

// Bring rights in, and make the edge shorter
destinationPoints[2] = vector_float2(0.9, 0.1)
destinationPoints[5] = vector_float2(0.9, 0.4)
destinationPoints[8] = vector_float2(0.9, 0.9)

// Make the center edge longer
destinationPoints[1] = vector_float2(0.5, -0.2)
destinationPoints[4] = vector_float2(0.5, 0.7)
destinationPoints[7] = vector_float2(0.5, 1.2)

// Need to set the no warp geometry first
effectNode.warpGeometry = SKWarpGeometryGrid(columns: 2, rows: 2)

let newGeometry = SKWarpGeometryGrid(columns: 2, rows: 2,
                                         sourcePositions: sourcePoints,
                                         destinationPositions: destinationPoints)

effectNode.run(SKAction.warp(to: newGeometry, duration: 1.0)!)

使用的来源:

https://www.hackingwithswift.com/example-code/games/how-to-warp-a-sprite-using-skwarpgeometrygrid

http://sound-of-silence.com/?page=blog&sort=recent&count=0

2) Live Copy 方法

这更简单,代码更少,用途更广泛;你可以在这个雷曼传奇关卡https://youtu.be/7m5YQrucis8?t=1226 中做类似的事情。此外,它还有效。

基本上,我们的主 SKView 显示主场景。我们将添加一个显示复制场景的副本 SKView。复制场景中只有一个精灵节点。 在主场景的更新循环中,我们将抓取场景的扁平纹理并将其分配给复制场景中的精灵节点。作为场景委托的对象可以进入这个更新循环,因此我们将使视图控制器符合 SKSceneDelegate。

class Playground_VC: UIViewController, SKSceneDelegate {

    var sprite: SKSpriteNode?

    override func loadView() {
        self.view = SKView()
    }

    var skView: SKView {
        return self.view as! SKView
    }

    override func viewWillAppear(_ animated: Bool) {
        let scene = SKScene(size: self.skView.bounds.size)
        scene.backgroundColor = UIColor.clear
        scene.delegate = self
        self.skView.presentScene(scene)

        let e = newEmitter()
        scene.addChild(e)
        e.position = self.skView.midPoint

        self.createCopy()

    }

    func update(_ currentTime: TimeInterval, for scene: SKScene) {
        // Grab the root texture and assign to the sprite that's in the copy scene
        let t = self.skView.texture(from: self.skView.scene!)
        self.sprite?.texture = t
    }

    func createCopy() {

        // Make a new SKView that's 4 times smaller

        var f = self.skView.bounds
        f.size.height *= 0.4
        f.size.width *= 0.4

        let copy = SKView(frame: f)
        copy.presentScene(SKScene(size: f.size))
        self.view.addSubview(copy)

        let sprite = SKSpriteNode(texture: nil, size: f.size)
        copy.scene?.addChild(sprite)
        self.sprite = sprite
        sprite.position = CGPoint(x: f.size.width / 2, y: f.size.height / 2)

    }

    func newEmitter() -> SKEmitterNode {
        return SKEmitterNode(fileNamed: "Particle.sks")!
    }
}

您在主 SKView 上执行的任何操作,例如在 3D 空间中倾斜它,都不应影响副本的外观,因为游戏场景不受这些操作的影响。

【讨论】:

  • 太棒了! (你知道,我认为这基本上是 SK 案例的 2 个完整解决方案 - 太棒了。我想知道 UIView 案例是否真的有任何方法)
【解决方案2】:

我可以想到几件事来尝试:

将 UIView 的内容复制到某处:您可以截屏:

...到图像:

UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 
                                       UIScreen.mainScreen().scale)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

...或视图(根据Apple的文档比上述更快):

func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView?

最后,这个项目相当老旧,无人维护,并且是用 Obj-C 编写的,但也许它能让你了解他们是如何为你自己做的:

https://github.com/honcheng/PaperFold-for-iOS

【讨论】:

  • 啊,九,谢谢你的回答,但我当然想做一个真实的视图!一个 UIView。 (渲染静态图像并弯曲它真的很简单!)
【解决方案3】:

这篇文章没有回答如何在 3D 中折叠 UIView。 OP提到:

“本质上,如果有人知道如何将一个 UIView “复制”到另一个, 嗯,观点,什么的……问题就解决了。”

如果复制视图可以解决这个问题,那么你应该看看这个question 我试过这个answer,它工作正常。我试了一下:

上述输出的代码:

import UIKit
class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var rightView: UIView!
    @IBOutlet weak var leftView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        textField.addTarget(self, action: #selector(didChange(textField:)), for: .editingChanged)
    }

    @objc func didChange(textField: UITextField){
        rightView.subviews.forEach{$0.removeFromSuperview()}
        let v = (try? leftView.copyObject()) as? UIView ?? UIView()
        rightView.addSubview(v)
        v.translatesAutoresizingMaskIntoConstraints = true
        NSLayoutConstraint.activate([
            v.topAnchor.constraint(equalTo: rightView.topAnchor),
            v.leadingAnchor.constraint(equalTo: rightView.leadingAnchor),
            v.bottomAnchor.constraint(equalTo: rightView.bottomAnchor),
            v.trailingAnchor.constraint(equalTo: rightView.trailingAnchor)
        ])
    }
}

extension NSObject {
    func copyObject<T:NSObject>() throws -> T? {
        let data = try NSKeyedArchiver.archivedData(withRootObject:self, requiringSecureCoding:false)
        return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T
    }
}

【讨论】:

  • @downvoter,当您对答案投反对票时,请提及其背后的原因。也许这很明显.. 但它仍然有助于改善某人的答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-14
  • 2013-06-16
  • 2013-09-20
  • 2018-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多