如何在Swift的SceneKit中为对象添加内边距
SceneKit对象始终占满容器,如何添加内边距或调整大小?
我在用SceneKit开发功能,但不管怎么设置,场景里的对象总会占满容器全部尺寸。我想通过添加四周内边距或动态调整对象大小来改变这个情况,目前用圆形图片测试,但对象始终适配SceneKit容器的尺寸。
SceneKitView代码
struct SceneKitView: UIViewRepresentable { let imageName: String func makeUIView(context: Context) -> SCNView { let sceneView = SCNView() sceneView.scene = makeScene() sceneView.allowsCameraControl = true sceneView.backgroundColor = UIColor.black return sceneView } func updateUIView(_ uiView: SCNView, context: Context) { // No dynamic updates needed yet } private func makeScene() -> SCNScene { let scene = SCNScene() let badgeNode = createBadgeNode(from: imageName) scene.rootNode.addChildNode(badgeNode) return scene } private func createBadgeNode(from imageName: String) -> SCNNode { guard let image = UIImage(named: imageName) else { fatalError("Image \(imageName) not found in assets.") } let screenWidth = UIScreen.main.bounds.width let screenHeight = UIScreen.main.bounds.height let horizontalPadding: CGFloat = 40 // <-- 无效果 let maxBadgeWidth = screenWidth - horizontalPadding let aspectRatio = image.size.width / image.size.height let badgeWidth = Float(min(maxBadgeWidth, screenHeight * aspectRatio)) / 1000 let badgeHeight = badgeWidth / Float(aspectRatio) let badgeGeometry = SCNPlane(width: CGFloat(badgeWidth), height: CGFloat(badgeHeight)) let badgeMaterial = SCNMaterial() badgeMaterial.diffuse.contents = image badgeMaterial.isDoubleSided = true badgeGeometry.materials = [badgeMaterial] let badgeNode = SCNNode(geometry: badgeGeometry) badgeNode.position = SCNVector3(0, 0, 0) return badgeNode } }
ContentView代码
struct ContentView: View { @State private var selectedImage: UIImage? var body: some View { VStack { SceneKitView(imageName: "Test1") .frame(width: 300, height: 300) //.edgesIgnoringSafeArea(.all) } } }
尝试的调整及效果
- 默认代码效果:圆形图片完全填满300x300的容器
- 仅修改width参数:
效果:图片高度被拉伸,形状变形let badgeGeometry = SCNPlane(width: CGFloat(badgeWidth) + 0.1, height: CGFloat(badgeHeight)) // 其余代码不变 - 同时修改宽高参数:
效果:图片整体被拉伸,严重扭曲let badgeGeometry = SCNPlane(width: CGFloat(badgeWidth) + 0.1, height: CGFloat(badgeHeight) + 0.1) // 其余代码不变 - 进一步调整height参数:
效果:图片高度被过度拉伸,形状完全变形let badgeGeometry = SCNPlane(width: CGFloat(badgeWidth) + 0.1, height: CGFloat(badgeHeight) + 0.5) // 其余代码不变
解决方案
问题核心是SceneKit默认相机会自动适配场景内容,导致对象填满视图。以下是两种有效解决方式:
1. 手动设置相机参数,预留内边距
在makeScene()中添加自定义相机,通过控制相机位置和视野,让对象周围留出空间:
private func makeScene() -> SCNScene { let scene = SCNScene() let badgeNode = createBadgeNode(from: imageName) scene.rootNode.addChildNode(badgeNode) // 添加自定义相机 let cameraNode = SCNNode() cameraNode.camera = SCNCamera() // 预留20%内边距,根据对象大小调整相机z轴位置 let paddingFactor: Float = 1.2 cameraNode.position = SCNVector3(0, 0, badgeNode.boundingSphere.radius * paddingFactor) // 让相机始终对准对象 cameraNode.look(at: badgeNode.position) scene.rootNode.addChildNode(cameraNode) return scene }
如果需要保留用户相机控制,可调整相机控制的限制范围;不需要则直接关闭allowsCameraControl。
2. 基于容器大小计算对象尺寸,保持比例
修正createBadgeNode的尺寸计算逻辑,以容器大小为基准,而非屏幕大小,同时严格保持图片原始比例:
// 新增containerSize参数,传递容器尺寸 private func createBadgeNode(from imageName: String, containerSize: CGSize) -> SCNNode { guard let image = UIImage(named: imageName) else { fatalError("Image \(imageName) not found in assets.") } let padding: CGFloat = 40 let maxAvailableSize = CGSize( width: containerSize.width - padding*2, height: containerSize.height - padding*2 ) let aspectRatio = image.size.width / image.size.height // 计算符合比例的最大尺寸 var badgeSize = CGSize() if image.size.width > image.size.height { badgeSize.width = maxAvailableSize.width badgeSize.height = maxAvailableSize.width / aspectRatio } else { badgeSize.height = maxAvailableSize.height badgeSize.width = maxAvailableSize.height * aspectRatio } // 转换为SceneKit合理单位(1单位≈1米,缩小尺寸适配场景) let scnWidth = Float(badgeSize.width) / 100 let scnHeight = Float(badgeSize.height) / 100 let badgeGeometry = SCNPlane(width: CGFloat(scnWidth), height: CGFloat(scnHeight)) let badgeMaterial = SCNMaterial() badgeMaterial.diffuse.contents = image badgeMaterial.isDoubleSided = true badgeGeometry.materials = [badgeMaterial] let badgeNode = SCNNode(geometry: badgeGeometry) badgeNode.position = SCNVector3(0, 0, 0) return badgeNode }
然后在makeScene和makeUIView中传递容器尺寸:
func makeUIView(context: Context) -> SCNView { let sceneView = SCNView() // 传递容器尺寸(也可通过参数动态传入) sceneView.scene = makeScene(containerSize: CGSize(width: 300, height: 300)) sceneView.allowsCameraControl = true sceneView.backgroundColor = UIColor.black return sceneView } private func makeScene(containerSize: CGSize) -> SCNScene { let scene = SCNScene() let badgeNode = createBadgeNode(from: imageName, containerSize: containerSize) scene.rootNode.addChildNode(badgeNode) // 添加相机确保对象显示在视野内 let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(0, 0, badgeNode.boundingSphere.radius * 1.2) cameraNode.look(at: badgeNode.position) scene.rootNode.addChildNode(cameraNode) return scene }
内容的提问来源于stack exchange,提问作者Yo19
相关产品推荐
相关产品推荐

