Prevent transparent NSWindow with a ContentView using blendMode from flickering - xcode

The following creates a transparent NSWindow with a ContentView that uses blendMode to create a colour filter overlay effect so that everything behind the window appears blended (grey monochrome in this instance). It's working as expected except when the window is not active or being dragged in which case the ContentView flickers between normal (no blending) and blended; the ContentView is also showing dirty in some cases, i.e. when inactive ContentView is partially rendering and not fully updated.
Am I missing something in terms of ContentView life-cycle / refresh in relation to NSWindow events, is my NSWindow setup correct, or is this a potential bug? Essentially, the issue doesn't occur when blendMode isn't used, as testing with a transparent NSWindow and semi-opaque ContentView behaves normally.
I'm using Xcode 12.5.1 on Big Sur 11.6.2, and targeting 10.15
Code to reproduce using the AppKit App Delegate lifecycle template:
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
.edgesIgnoringSafeArea(.top)
.blendMode(BlendMode.color)
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.setFrameAutosaveName("Main Window")
window.isOpaque = false
window.backgroundColor = .clear
window.level = .floating
window.isMovable = true
window.isMovableByWindowBackground = true
window.titlebarAppearsTransparent = true
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
UPDATE 7th March
Issue persists using Xcode 13.2.1 on Monterey 12.2.1, and targeting 12.2
UPDATE 8th March
Adding a default NSVisualEffectView background view results in much greater stability in that the view no longer flickers between opaque and transparent when the window is active and being dragged.
The only issue remaining is when switching between apps and the focus is lost which sometimes causes the view to become opaque, although refocusing the window fixes the problem.
A workaround is to enable hidesOnDeactivate on NSWindow, combined with applicationShouldHandleReopen, so the window disappears when focus is lost and the issue isn't visible to the user, but ideally the window should remain visible at all times until closed.
struct VisualEffectView: NSViewRepresentable {
func makeNSView(context: Context) -> NSVisualEffectView {
let view = NSVisualEffectView()
return view
}
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(VisualEffectView())
}
}

This feels a bit hackish and I'm sure someone out there with greater knowledge has a more elegant solution, but adding a Timer to the view to force a redraw solves the flickering problem completely, and would therefore appear to answer the question. Note: this method also dispenses with the need for a dummy NSVisualEffectView.
struct ContentView: View {
#State var currentDate = Date()
let timer = Timer.publish(every: 1.0 / 60.0, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
Rectangle()
.fill(.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Text("\(currentDate)")
.foregroundColor(.clear)
.onReceive(timer) { input in
currentDate = input
}
}
}
}

Related

Drag degradation when SwiftUI macOS [Toolbar -> ToolbarItem -> Button -> Action] contains #State variable reference

2021/10/28 Update:
Fixed when building with:
macOS 12.0.1 (21A559)
Xcode 13.1 (13A1030d)
2021/10/19 Update:
Bug has been submitted to Apple - FB9713387
Another user has tweeted about this.
Could anyone shed some light on why referencing a #State variable inside the following introduces drag degradation/choppiness, or a workaround?
I have tried the following:
Different variable types
Using the variable within the action and not using it
Running same code on iOS - no lag at all.
Xcode Running Xcode Version 13.0 (13A233)
Code
struct ContentView: View {
#State private var iAmAnUnusedBool: Bool = true
#State private var dragOffset = CGSize.zero
#State private var width: CGFloat = 200
var body: some View {
HStack(alignment: .top, spacing: 0) {
TextEditor(text: .constant("Left Panel"))
Rectangle()
.frame(width: 6)
.onHover { $0 ? NSCursor.resizeLeftRight.push() : NSCursor.pop() }
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .named("contentView"))
.onChanged { gesture in
dragOffset = gesture.translation
}
.onEnded { gesture in
let dragOffsetOriginal = dragOffset.width
dragOffset = .zero
width = width - dragOffsetOriginal
}
)
TextEditor(text: .constant("Right panel"))
.frame(width: width - dragOffset.width)
}
.toolbar {
ToolbarItem {
Button("Button") {
iAmAnUnusedBool // THIS INTRODUCES DEGRADATION
}
}
}
.coordinateSpace(name: "contentView")
}
}
No Lag
Given iAmAnUnusedBool is commented out
When I drag the Rectangle() back and forth
Then there is no lag
Lag
Given iAmAnUnusedBool is uncommented
When I drag the Rectangle() back and forth
Then there is lag (appears 1 minute after rapid dragging)

Core Data template app generates errors on save

While evaluating Core Data for a macOS app, I get errors logged to the console when the document is saved:
[logging-persist] cannot open file at line 39930 of [02c344acea]
[logging-persist] os_unix.c:39930: (0) openDirectory(/Users/seanrich/Desktop) - Undefined error: 0
The data is saved properly and there's no other indication of an issue, but that error is concerning.
This sample project demonstrates the issue. It's minimally changed from a template project (selecting SwiftUI as the interface, Core Data checked). There's a single entity, Item, with a single attribute, name. I added a minimal interface to add items into the NSManagedObjectContext:
struct ContentView: View {
#State private var name = ""
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Item.entity(), sortDescriptors: [.byName]) var items: FetchedResults<Item>
var body: some View {
VStack {
HStack {
TextField("Name", text: $name)
Button("New") {
let item = Item(context: moc)
item.name = name
}
}
.padding()
VStack {
ForEach(items) { item in
Text(item.name ?? "No name")
}
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
The NSPersistentDocument subclass is unchanged from the template:
EDIT: I added a line to explicitly load the managedObjectModel or the SwiftUI view tries to fetch an entity that isn't there yet:
class Document: NSPersistentDocument {
override class var autosavesInPlace: Bool { true }
override func makeWindowControllers() {
_ = managedObjectModel
let contentView = ContentView().environment(\.managedObjectContext, self.managedObjectContext!)
// Create the window and set the content view.
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.contentView = NSHostingView(rootView: contentView)
let windowController = NSWindowController(window: window)
self.addWindowController(windowController)
}
}
I thought it might be a permissions issue, but the sandbox is set to give read/write access to user-selected files. Couldn't find any mention of this issue anywhere which is surprising since it seems to be default behavior. Makes me think I'm missing something obvious.

How can I hide statusBar of a View/Window in macOS in SwiftUI?

I was trying to hide statusBar of a view but I noticed it does not work in macOS, also Blur did not worked also opacity did not work! it look like I am trying programming new language, apple said code in place and apply every where! why I can not done those simple task in macOS?
struct ContentView: View {
var body: some View {
HStack {
Button { print("OK was clicked!") } label: { Text("OK").frame(width: 100) }
Button { print("Close was clicked!") } label: { Text("Close").frame(width: 100) }
}
.frame(width: 400, height: 200)
.background(Color.white.opacity(0.4).blur(radius: 0.5))
//.statusBar(hidden: true)
}
}
import SwiftUI
#main
struct macOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Hope it helps you.
Thank you.
class AppDelegate: NSObject, UIApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the content view, to display the contents
let contentView = ContentView()
//to display content view beyond status bar.
.edgesIgnoringSafeArea(.top)
// Now, just create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .texturedBackground, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
//So, this does the maigc
//Hides the titlebar.
window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
}

SwiftUI ScrollView adds unwanted animation automatically

I have an issue with animations within a SwiftUI ScrollView. I can reproduce it in a Playground with the code seen below. I just want to animate the opacity but it also animates the scaling. If I use a VStack instead of a ScrollView it works. But I need it to be scrollable.
Did someone experienced the same issue and could give me a quick hint?
Actual behaviour: https://giphy.com/gifs/h8DSbS1xZ9PJyHIJrY
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
#State var showText = 0.0
var body: some View {
ScrollView {
Text("Test")
.font(.title)
.opacity(showText)
Text("Another really really long text")
.opacity(showText)
}
.frame(width: 320, height: 420)
.background(Color.red)
.onAppear {
withAnimation(Animation.easeInOut(duration: 1)) {
self.showText = 1.0
}
}
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
Here is possible solution. Tested with Xcode 11.4 / iOS 13.4
ScrollView {
VStack {
Text("Test")
.font(.title)
Text("Another really really long text")
}
.fixedSize()
.opacity(showText)
}

NSWindow contentView not cover full window size - macOS & SwiftUI

I'm starting a new macOS app with SwiftUI but I have a big problem.
The app needs a full size contentView (underneath titleBar) but I can't accomplish. On a new project using Storyboards works fine, but with SwiftUI not.
My code:
Result:
And it should look like this:
Any ideas?
Thanks!
I just used the following variant in AppDelegate, the content of ContentView and others can be any
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
.edgesIgnoringSafeArea(.top) // to extend entire content under titlebar
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .texturedBackground, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.titlebarAppearsTransparent = true // as stated
window.titleVisibility = .hidden // no title - all in content
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
The safe area does not extend underneath a transparent title bar. You can use edgesIgnoringSafeArea to tell your content view edges to ignore the safe area. Something that resembles your example:
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Text("Hello, World!")
.frame(maxWidth: 200, maxHeight: .infinity)
.background(Color.red)
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black)
}.edgesIgnoringSafeArea(.all)
}
}
Update:
If you want to use a NavigationView, you have to add edgesIgnoringSafeArea to its contents as well:
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.frame(maxWidth: 200, maxHeight: .infinity)
.background(Color.red)
.edgesIgnoringSafeArea(.all)
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black)
.edgesIgnoringSafeArea(.all)
}.edgesIgnoringSafeArea(.all)
}
}
Unfortunately, this will initially show a title bar at the moment, apparently until you force a full window redraw. Once you move the window to a different display or hide and show it again, the title bar disappears. So, my guess is that this will be fixed at some point.
Right now, you can programmatically force a hide & show by adding
DispatchQueue.main.async {
self.window.orderOut(nil)
self.window.makeKeyAndOrderFront(nil)
}
after window.makeKeyAndOrderFront(nil) in applicationDidFinishLaunching. It will add a very short animation however. If it bothers you, you may be able to turn that off with NSWindow.animationBehavior or something like that.
Update 2: Apparently, if you remove the initial window.makeKeyAndOrderFront(nil) and replace it with the dispatch queue logic above it won't animate. So in the end you'll have
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ContentView()
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView, .texturedBackground],
backing: .buffered, defer: false)
window.titlebarAppearsTransparent = true
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
// window.makeKeyAndOrderFront(self) <- don't call it here
DispatchQueue.main.async {
self.window.orderOut(nil)
self.window.makeKeyAndOrderFront(nil)
}
}
Just for more information in the case of SwiftUI App life cycle.
You need to set the window style to HiddenTitleBarWindowStyle :
WindowGroup {
ContentView()
}.windowStyle(HiddenTitleBarWindowStyle())
Minimal solution in pure SwiftUI.
#main
struct X_App: App {
var body: some Scene {
WindowGroup {
ContentView()
.edgesIgnoringSafeArea(.top)
}.windowStyle(.hiddenTitleBar)
}}

Resources