You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在打开UIImagePicker后保留ARKit场景中的节点

解决打开照片库/相机后之前添加的节点消失问题

嘿,这个问题我之前也碰到过!大概率是因为UIImagePickerController弹出后,你的当前视图控制器被系统销毁并重建了(尤其是在iPad上,或者内存紧张的场景下),导致之前添加的节点没有被持久化保存,所以回到页面后就全部消失了。下面是具体的解决思路和代码调整方案:

核心解决思路

我们需要把所有已添加的节点(文本、图片等)的状态持久化保存起来,在视图控制器重新加载时恢复这些节点。

1. 定义节点模型

首先创建一个Node模型类,用来存储每个节点的关键信息:

import UIKit

enum NodeType {
    case text
    case image
}

class Node: Codable {
    var type: NodeType
    var content: String // 文本内容或图片本地路径
    var frame: CGRect
    
    init(type: NodeType, content: String, frame: CGRect) {
        self.type = type
        self.content = content
        self.frame = frame
    }
}

2. 保存与恢复节点状态

在你的视图控制器中添加一个数组来存储节点,并重写视图生命周期方法来处理保存和恢复:

class YourViewController: UIViewController {
    // 存储所有节点的数组
    private var nodes: [Node] = []
    // 用来添加节点的父视图(假设你有一个这样的视图容器)
    @IBOutlet weak var canvasView: UIView!

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // 保存当前所有节点状态
        saveNodes()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // 恢复之前保存的节点
        restoreNodes()
    }

    private func saveNodes() {
        // 遍历canvasView上的所有子视图,转换成Node模型
        nodes.removeAll()
        for subview in canvasView.subviews {
            if let textView = subview as? UITextView {
                let textNode = Node(type: .text, content: textView.text, frame: textView.frame)
                nodes.append(textNode)
            } else if let imageView = subview as? UIImageView {
                // 把图片保存到本地沙盒,存储路径
                if let image = imageView.image, let imagePath = saveImageToLocal(image: image) {
                    let imageNode = Node(type: .image, content: imagePath, frame: imageView.frame)
                    nodes.append(imageNode)
                }
            }
        }
        // 把nodes数组归档存储到UserDefaults(或者用CoreData/Realm更复杂的存储)
        if let encodedData = try? JSONEncoder().encode(nodes) {
            UserDefaults.standard.set(encodedData, forKey: "savedNodes")
        }
    }

    private func restoreNodes() {
        // 清空当前canvasView的子视图
        canvasView.subviews.forEach { $0.removeFromSuperview() }
        // 从UserDefaults取出保存的节点数据
        guard let encodedData = UserDefaults.standard.data(forKey: "savedNodes"),
              let savedNodes = try? JSONDecoder().decode([Node].self, from: encodedData) else {
            return
        }
        nodes = savedNodes
        // 重新创建并添加节点到canvasView
        for node in nodes {
            switch node.type {
            case .text:
                let textView = UITextView(frame: node.frame)
                textView.text = node.content
                // 这里添加你之前设置textView的样式代码
                canvasView.addSubview(textView)
            case .image:
                let imageView = UIImageView(frame: node.frame)
                imageView.image = UIImage(contentsOfFile: node.content)
                imageView.contentMode = .scaleAspectFit
                canvasView.addSubview(imageView)
            }
        }
    }

    // 辅助方法:保存图片到本地沙盒
    private func saveImageToLocal(image: UIImage) -> String? {
        guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
            return nil
        }
        let fileName = UUID().uuidString + ".png"
        let fileURL = documentsDirectory.appendingPathComponent(fileName)
        if let data = image.pngData() {
            do {
                try data.write(to: fileURL)
                return fileURL.path
            } catch {
                print("保存图片失败:\(error)")
                return nil
            }
        }
        return nil
    }
}

3. 调整你的弹窗与图片选择代码

另外,你的弹窗代码里有个小问题:手动调用alert.dismiss(animated: true, completion: nil)是多余的,因为present(picker, animated: true)会自动把当前的alert dismiss掉。同时,在图片选择完成后,要记得把新的图片节点添加到nodes数组并保存:

// 实现UIImagePickerControllerDelegate方法
extension YourViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        picker.dismiss(animated: true) { [weak self] in
            guard let self = self,
                  let editedImage = info[.editedImage] as? UIImage else {
                return
            }
            // 创建图片视图(这里可以自定义位置和大小)
            let imageView = UIImageView(frame: CGRect(x: 50, y: 50, width: 200, height: 200))
            imageView.image = editedImage
            imageView.contentMode = .scaleAspectFit
            self.canvasView.addSubview(imageView)
            // 保存新的图片节点到数组
            if let imagePath = self.saveImageToLocal(image: editedImage) {
                let imageNode = Node(type: .image, content: imagePath, frame: imageView.frame)
                self.nodes.append(imageNode)
                self.saveNodes() // 立即保存
            }
        }
    }

    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true)
    }
}

// 调整你的弹窗代码
func showActionSheet() {
    let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
    alert.addAction(UIAlertAction(title: "Add Text", style: .default , handler:{ [weak self] _ in
        guard let self = self else { return }
        self.addTextView.isHidden = false
        self.inputTextField.becomeFirstResponder()
        // 注意:当用户输入完成添加文本节点后,也要把文本节点添加到nodes数组并调用saveNodes()
    }))
    alert.addAction(UIAlertAction(title: "Choose from Library", style: .default , handler:{ [weak self] _ in
        guard let self = self else { return }
        let picker = UIImagePickerController()
        picker.delegate = self
        picker.allowsEditing = true
        picker.sourceType = .photoLibrary
        self.present(picker, animated: true)
    }))
    alert.addAction(UIAlertAction(title: "Take a Photo", style: .default , handler:{ [weak self] _ in
        guard let self = self else { return }
        let picker = UIImagePickerController()
        picker.delegate = self
        picker.allowsEditing = true
        picker.sourceType = .camera
        self.present(picker, animated: true)
    }))
    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
    
    // 适配iPad的弹窗展示
    if let popoverController = alert.popoverPresentationController {
        popoverController.sourceView = self.view
        popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
    }
    
    self.present(alert, animated: true)
}

额外注意点

  • 如果你是在addTextView的输入完成后添加文本节点,一定要记得把文本节点的信息添加到nodes数组并调用saveNodes()方法。
  • 存储方式如果觉得UserDefaults不够安全或容量小,可以换成CoreData或者Realm来存储节点数据。
  • 适配iPad的弹窗,避免崩溃(上面的代码已经添加了popover的处理)。

内容的提问来源于stack exchange,提问作者Ruslan Pitula

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:10:19