The configuration closure allows you to customize the appearance and layout of the transition layer. There are three levels of control depending on how much manual layout work you want to perform:
Level 1: Styling Only
Use this to modify appearance (like corner radius or shadows) without affecting positioning. The system automatically applies frame and offset after your configuration.
Level 2: Full Control (Interpolated Values)
Use this when you need to control the order of modifiers (e.g., applying a clip shape after a frame). You must manually apply the frame and offset using the provided size and position parameters.
Level 3: Raw Source/Destination Values
Use this for complex custom interpolation. You receive both the source and destination values for size and position, allowing you to calculate custom paths or logic.
// Level 1: Styling Only
.portalTransition(item: $selectedItem, in: namespace) { item in
ItemView(item: item)
} configuration: { content, isActive in
content
.clipShape(.rect(cornerRadius: isActive ? 24 : 12, style: .continuous))
.shadow(radius: isActive ? 10 : 2)
}
// Level 2: Full Control (Interpolated Values)
.portalTransition(item: $selectedItem, in: namespace) { item in
ItemView(item: item)
} configuration: { content, isActive, size, position in
content
.frame(width: size.width, height: size.height)
.clipShape(.rect(cornerRadius: isActive ? 24 : 12, style: .continuous))
.offset(x: position.x, y: position.y)
}
// Level 3: Raw Source/Destination Values
.portalTransition(item: $selectedItem, in: namespace) { item in
ItemView(item: item)
} configuration: { content, isActive, sourceSize, destinationSize, sourcePosition, destinationPosition in
let size = isActive ? destinationSize : sourceSize
let position = isActive ? destinationPosition : sourcePosition
return content
.frame(width: size.width, height: size.height)
.offset(x: position.x, y: position.y)
}