Is there any way to specify a clip volume or clipping planes on either a RealityView or the underlying RealityKit entity on visionOS? This was easy on SceneKit with shader modifiers, or in OpenGL, or WebGL, or with RealityKit on iOS or macOS with CustomMaterial surface shader, but CustomMaterial is not supported on visionOS.
Post
Replies
Boosts
Views
Activity
In a SwiftUI VisionOS app that opens with one .plain window and another .volumetric window, I want to automatically close the Volume when the user closes the plain window. Attempting to do so with
ContentView().onDisappear{ dismissWindow(id: kVolumeId) }
results in
Page tried to end immersive session initiated by a different page
I have not found that error documented anywhere. Any advice?
In trying to implement a custom variation of HSplitView/VSplitView for macOS, I found it impossible to achieve correct behavior of the NSCursor while dragging the split view divider. Something else is resetting NSCursor to an arrow cursor when a SwiftUI view updates.
Moving the mouse over the divider View changes the NSCursor to a resize cursor.
However, when the insertion is blinking, after 1/2 a second, on the blink the cursor changes back to an arrow.
Turning off "blink", then hovering over the divider, the NSCursor stays as a resize cursor. However, as soon as you click and drag to move the divider, again it changes back to an arrow cursor, flashing between the two NSCursor as you move the divider.
It seems like something in SwiftUI is doing this, but I can't tell.
Is it possible to implement a custom VSplitView within SwiftUI and achieve consistent behavior of the NSCursor?
Full code below
import AppKit
import SwiftUI
@main
struct NSCursorBugApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
let blinkTimer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
struct ContentView: View {
@State var position = 20.0
@State var opacity = 1.0
@State var blink = true
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Color.gray
.frame(height: position - 4)
.overlay{
Rectangle().frame(width: 1, height: 20).opacity(opacity).padding(.trailing, 200).padding(20)
}
PaneDivider(position: $position)
Color.cyan
Toggle("Blink", isOn: $blink)
}
.onReceive(blinkTimer) {_ in if blink { opacity = 1 - opacity } }
.frame(width: 500, height: 500)
.coordinateSpace(name: "stack")
}
}
struct PaneDivider: View {
@Binding var position: Double
let dividerSize = 8.0
let circleSize = 6.0
var body: some View {
Group {
Divider()
ZStack {
Rectangle().fill(Color(white: 0.99))
Circle().frame(width: circleSize, height: circleSize).foregroundColor(Color(white: 0.89))
}.frame(height: 8)
Divider()
}
#if os(macOS)
.background(NSCursorView())
#endif
.gesture(DragGesture(minimumDistance: 1, coordinateSpace: .named("stack"))
.onChanged { position = max(0, $0.location.y) })
}
}
/// Helper NSView as .background to SwiftUI PaneDivider View to set resize NSCursor when inside view bounds
class NSCursorPlatformView : NSView {
var horizontal = true
var trackingArea: NSTrackingArea? = nil
func clearTrackingAreas() {
if let trackingArea = trackingArea { removeTrackingArea(trackingArea) }
trackingArea = nil
}
override func updateTrackingAreas() {
clearTrackingAreas()
let area = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .mouseMoved, .activeInKeyWindow], owner: self, userInfo: nil)
addTrackingArea(area)
trackingArea = area
}
override func viewWillMove(toWindow newWindow: NSWindow?) {
if newWindow == nil { clearTrackingAreas() }
else { updateTrackingAreas() }
}
override func mouseEntered(with event: NSEvent) { updateCursor(with: event) }
override func mouseExited(with event: NSEvent) { updateCursor(with: event) }
override func mouseMoved(with event: NSEvent) { updateCursor(with: event) }
override func cursorUpdate(with event: NSEvent) {} // to stop system from resetting cursor after us
func updateCursor(with event: NSEvent) {
let p = convert(event.locationInWindow, from: nil)
if bounds.contains(p) {
(horizontal ? NSCursor.resizeUpDown : NSCursor.resizeLeftRight).set()
} else {
NSCursor.arrow.set()
}
}
}
struct NSCursorView: NSViewRepresentable {
var horizontal = true
func makeNSView(context: Context) -> NSCursorPlatformView {
let view = NSCursorPlatformView()
view.horizontal = horizontal
return view
}
func updateNSView(_ nsView: NSCursorPlatformView, context: Context) { }
}
Due to limitations of SwiftUI VSplitView, and to preserve the appearance of the old C++/AppKit app I'm porting to SwiftUI, I rolled my own pane divider. It worked well on macOS 11, but after updating to macOS 12, it now triggers an infinite loop somewhere. Running the code below in an Xcode playground works for a short while, but if you wiggle the mouse up and down, after a few seconds it will get caught in an infinite loop:
import SwiftUI
import PlaygroundSupport
import AppKit
import CoreGraphics
PlaygroundPage.current.setLiveView(ContentView())
struct ContentView: View {
@State var position: Double = 50
var body: some View {
VStack(spacing: 0) {
Color.red
.frame(maxHeight: position)
PaneDivider(position: $position)
Color.blue
}
.frame(width: 500, height:500)
}
}
struct PaneDivider: View {
@Binding var position: Double
@GestureState private var isDragging = false // Will reset to false when dragging has ended
var body: some View {
Group {
Divider()
ZStack {
Rectangle().fill(Color(white: 0.99))
Circle().frame(width: 6, height: 6).foregroundColor(Color(white: 0.89))
}.frame(height: 8)
Divider()
}
.gesture(DragGesture()
.onChanged {
position += $0.translation.height
if position < 0 { position = 0 }
}
.updating($isDragging) { (value, state, transaction) in state = true })
}
}
Any thoughts on how to diagnose or fix that?
Graphing Calculator is a SwiftUI macOS FileDocument-based app. There is a slider that can be set to animate on loop, changing a value continuously animating equations. When it is playing the View menu, and Open Recent, Revert and Share submenus flicker and vanish.
The animation is changing a published property of an ObservableObject in the FileDocument.
Any advice on how to go about fixing this in the app?
Is there a way to be notified within the SwiftUI app lifecycle, when the user clicks in the macOS menu bar in order to pause the animation for the duration of the menu selection?
I'm trying to animate properties of an @ObservedObject in an NSViewRepresentable using withAnimation but the change happens instantly, and breakpoints in my animatableData get/setters are not touched.
Is that a reasonable thing to attempt?
When does SwiftUI look for animatableData?
Any advice on how to go about it?
Is it possible to set the isAlternate property on an NSMenuItem in the macOS menubar to implement dynamic menu items in a SwiftUI document-based app which uses CommandMenu to construct the menus?