Lab 025 – Moving Windows Should Be Easy
Is this supposed to happen?
This is a remix of Lab 024. I refactored the code a bit and added some gestures to interact with the objects.
- Tab will enable gravity for that entity
- Drag gesture to move them around
- Reset button to move entities back to their positions and reset physics
Video Demo
Full Lab Code
struct Lab025: View {
// Define a structure for blocks
struct BlockData {
let id: String
let position: SIMD3<Float>
let collisionSize: (width: Float, height: Float, depth: Float)
}
let blocks: [BlockData] = [
BlockData(id: "WindowHandle", position: [0, -0.13, 0], collisionSize: (0.3, 0.21, 0.001)),
BlockData(id: "WindowBackground", position: [0, 0, 0], collisionSize: (0.3, 0.21, 0.001)),
BlockData(id: "WindowTitle", position: [-0.102, 0.08, 0], collisionSize: (0.05, 0.03, 0.001)),
BlockData(id: "WindowButton", position: [0.102, 0.08, 0], collisionSize: (0.05, 0.03, 0.001)),
BlockData(id: "WindowList", position: [0, -0.02, 0], collisionSize: (0.26, 0.12, 0.001)),
BlockData(id: "WindowRow1", position: [0, 0.024, 0.0001], collisionSize: (0.26, 0.04, 0.001)),
BlockData(id: "WindowRow2", position: [0, -0.0189, 0.0001], collisionSize: (0.26, 0.04, 0.001)),
BlockData(id: "WindowRow3", position: [0, -0.064, 0.0001], collisionSize: (0.26, 0.04, 0.001))
]
// Add state to store the window entity
@State private var windowEntity: Entity?
var body: some View {
RealityView { content, attachments in
let floor = Entity()
floor.setPosition([0, 0, 0], relativeTo: nil)
let floorCollision = CollisionComponent(shapes: [ShapeResource.generateBox(width: 10, height: 0.01, depth: 10)])
floor.components.set(floorCollision)
var floorPhysics = PhysicsBodyComponent()
floorPhysics.mode = .static
floorPhysics.isAffectedByGravity = false
floor.components.set(floorPhysics)
content.add(floor)
let window = Entity()
window.setPosition([1, 1.5, -2], relativeTo: nil)
window.setScale([3, 3, 3], relativeTo: nil)
content.add(window)
// Store window entity in state
windowEntity = window
var entityDict: [String: Entity] = [:]
for component in blocks {
if let entity = attachments.entity(for: component.id) {
window.addChild(entity)
entity.setPosition(component.position, relativeTo: window)
entity.name = component.id
let collision = CollisionComponent(shapes: [
ShapeResource.generateBox(
width: component.collisionSize.width,
height: component.collisionSize.height,
depth: component.collisionSize.depth
)
])
entity.components.set(collision)
var physicsBody = PhysicsBodyComponent()
physicsBody.isAffectedByGravity = false
physicsBody.mode = .dynamic // Make all bodies dynamic
entity.components.set(physicsBody)
entity.components.set(InputTargetComponent())
entityDict[component.id] = entity
}
}
} update: { content, attachments in
// Add update closure for handling reset
} attachments: {
Attachment(id: "WindowHandle") {
Capsule()
.foregroundStyle(.white.opacity(0.4))
.frame(width: 100, height: 8)
}
Attachment(id: "WindowBackground") {
VStack {
EmptyView()
}
.frame(width: 400, height: 300)
.glassBackgroundEffect()
}
Attachment(id: "WindowTitle") {
Text("Fruits")
.font(.title)
.frame(height: 60)
}
Attachment(id: "WindowButton") {
Button(action: {
print("button pressed")
}, label: {
Image(systemName: "arrow.clockwise")
})
.glassBackgroundEffect()
}
Attachment(id: "WindowList") {
List {
Text("")
Text("")
Text("")
}
.frame(width: 400, height: 180)
}
Attachment(id: "WindowRow1") {
HStack{
Text("Apple")
Spacer()
}
.padding()
.frame(width: 350, height: 60)
.background(.thickMaterial)
.clipShape(
.rect(
topLeadingRadius: 18,
bottomLeadingRadius: 0,
bottomTrailingRadius: 0,
topTrailingRadius: 18
)
)
}
Attachment(id: "WindowRow2") {
HStack{
Text("Banana")
Spacer()
}
.padding()
.frame(width: 350, height: 60)
.background(.thickMaterial)
}
Attachment(id: "WindowRow3") {
HStack{
Text("Orange")
Spacer()
}
.padding()
.frame(width: 350, height: 60)
.background(.thickMaterial)
.clipShape(
.rect(
topLeadingRadius: 0,
bottomLeadingRadius:18,
bottomTrailingRadius:18,
topTrailingRadius: 0
)
)
}
}
.modifier(DragGestureImproved())
.gesture(tapExample)
}
var tapExample: some Gesture {
TapGesture()
.targetedToAnyEntity()
.onEnded { value in
if value.entity.name == "WindowButton" {
resetEntities()
} else {
var physicsBody = PhysicsBodyComponent()
physicsBody.isAffectedByGravity = true
value.entity.components.set(physicsBody)
}
}
}
// Add reset function
private func resetEntities() {
print("reset pressed")
guard let window = windowEntity else { return }
// Animate window position and rotation
window.move(
to: Transform(
scale: .init(repeating: 3),
rotation: .init(angle: 0, axis: [0, 1, 0]),
translation: [1, 1.5, -2]
),
relativeTo: nil,
duration: 0.5,
timingFunction: .easeInOut
)
// Reset all child entities relative positions
for block in blocks {
if let entity = window.findEntity(named: block.id) {
var physicsBody = PhysicsBodyComponent()
physicsBody.isAffectedByGravity = false
physicsBody.mode = .dynamic
entity.components.set(physicsBody)
// Animate each entity back to its original position
entity.move(
to: Transform(
scale: .one,
rotation: .init(angle: 0, axis: [0, 1, 0]),
translation: block.position
),
relativeTo: window,
duration: 0.5,
timingFunction: .easeInOut
)
// Reset physics motion
var motion = PhysicsMotionComponent()
motion.linearVelocity = .zero
motion.angularVelocity = .zero
entity.components.set(motion)
}
}
}
}Support our work so we can continue to bring you new examples and articles.
Download the Xcode project with this and many more labs from Step Into Vision.

Follow Step Into Vision