如何沿一条线“折叠” 3D UIView?

Posted

技术标签:

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

如果你有一个 UIView

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

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

或回到右边..

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

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

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

这很不雅,但确实有效。

但是……

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

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

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

问题会解决的。

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

有人对此有意见吗:

【问题讨论】:

不完全是你想要的,但你也许可以从这个developer.apple.com/documentation/quartzcore/careplicatorlayer中破解一些东西 这不是您可以与 CADisplayLink 一起使用的东西吗?即一个页面是真正的渲染,第二个页面只是在每个屏幕刷新率下复制原始页面的快照。 @DanielGalasko ,你真的应该把它当作任何答案,否则赏金将被浪费 真正令人着迷的事情,@Aris(也许应该将其作为任何形式的答案,因为否则赏金将被浪费) 【参考方案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,当您对答案投反对票时,请提及其背后的原因。也许这很明显.. 但它仍然有助于改善某人的答案

以上是关于如何沿一条线“折叠” 3D UIView?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 rgw 距离在 android 中沿一条线在 Google Maps 折线上添加标记?

如何卷曲/折叠 UIView 的底角

如何以编程方式折叠 UIView?

如何使用自动布局展开和折叠 UIView

CAD2012怎么用多线命令沿一条中线画对称的两条线

如果有长文本,则可滚动 UIView,否则不可折叠