Core Data template app generates errors on save - macos

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.

Related

Prevent transparent NSWindow with a ContentView using blendMode from flickering

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
}
}
}
}

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)
}
}

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)
}}

Change window size based on NavigationView in a SwiftUI macOS app

I'm using SwiftUI for a Mac app where the main window contains a NavigationView. This NavigationView contains a sidebar list. When an item in the sidebar is selected, it changes the view displayed in the detail view. The views presented in the detail view are different sizes which should cause the size of the window to change when they are displayed. However, when the detail view changes size the window does not change size to accommodate the new detail view.
How can I make the window size change according to the size of the NavigationView?
My example code for the app is below:
import SwiftUI
struct View200: View {
var body: some View {
Text("200").font(.title)
.frame(width: 200, height: 400)
.background(Color(.systemRed))
}
}
struct View500: View {
var body: some View {
Text("500").font(.title)
.frame(width: 500, height: 300)
.background(Color(.systemBlue))
}
}
struct ViewOther: View {
let item: Int
var body: some View {
Text("\(item)").font(.title)
.frame(width: 300, height: 200)
.background(Color(.systemGreen))
}
}
struct DetailView: View {
let item: Int
var body: some View {
switch item {
case 2:
return AnyView(View200())
case 5:
return AnyView(View500())
default:
return AnyView(ViewOther(item: item))
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(1...10, id: \.self) { index in
NavigationLink(destination: DetailView(item: index)) {
Text("Link \(index)")
}
}
}
.listStyle(SidebarListStyle())
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And here is what the example app looks like when the detail view changes size:
Here is demo of possible approach that works. I did it on one different view, because you will need to redesign your solution to adopt it.
Demo
1) The view requiring window animated resize
struct ResizingView: View {
public static let needsNewSize = Notification.Name("needsNewSize")
#State var resizing = false
var body: some View {
VStack {
Button(action: {
self.resizing.toggle()
NotificationCenter.default.post(name: Self.needsNewSize, object:
CGSize(width: self.resizing ? 800 : 400, height: self.resizing ? 350 : 200))
}, label: { Text("Resize") } )
}
.frame(minWidth: 400, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
}
}
2) Window's owner (in this case AppDelegate)
import Cocoa
import SwiftUI
import Combine
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var subscribers = Set<AnyCancellable>()
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ResizingView()
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), // just default
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
NotificationCenter.default.publisher(for: ResizingView.needsNewSize)
.sink(receiveCompletion: {_ in}) { [unowned self] notificaiton in
if let size = notificaiton.object as? CGSize {
var frame = self.window.frame
let old = self.window.contentRect(forFrameRect: frame).size
let dX = size.width - old.width
let dY = size.height - old.height
frame.origin.y -= dY // origin in flipped coordinates
frame.size.width += dX
frame.size.height += dY
self.window.setFrame(frame, display: true, animate: true)
}
}
.store(in: &subscribers)
}
...
Asperi's answer works for me, but the animation is not working on Big Sur 11.0.1, Xcode 12.2. Thankfully, the animation works if you wrap it in an NSAnimationContext:
NSAnimationContext.runAnimationGroup({ context in
context.timingFunction = CAMediaTimingFunction(name: .easeIn)
window!.animator().setFrame(frame, display: true, animate: true)
}, completionHandler: {
})
Also it should be mentioned that ResizingView and window don't have to be initialized inside AppDelegate; you can continue using SwiftUI's App struct:
#main
struct MyApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ResizingView()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow?
var subscribers = Set<AnyCancellable>()
func applicationDidBecomeActive(_ notification: Notification) {
self.window = NSApp.mainWindow
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
setupResizeNotification()
}
private func setupResizeNotification() {
NotificationCenter.default.publisher(for: ResizingView.needsNewSize)
.sink(receiveCompletion: {_ in}) { [unowned self] notificaiton in
if let size = notificaiton.object as? CGSize, self.window != nil {
var frame = self.window!.frame
let old = self.window!.contentRect(forFrameRect: frame).size
let dX = size.width - old.width
let dY = size.height - old.height
frame.origin.y -= dY // origin in flipped coordinates
frame.size.width += dX
frame.size.height += dY
NSAnimationContext.runAnimationGroup({ context in
context.timingFunction = CAMediaTimingFunction(name: .easeIn)
window!.animator().setFrame(frame, display: true, animate: true)
}, completionHandler: {
})
}
}
.store(in: &subscribers)
}
}
the following will not solve your problem, but might (with some extra work), lead you to a solution.
I did not have much to investigate further, but it's possible to overwrite the setContentSize method in NSWindow (by subclassing of course). That way you can override the default behavior, which is setting the window frame without an animation.
The problem you will have to figure out is the fact that for complex views such as yours, the setContentSize method is called repeatedly, causing the animation not to work properly.
The following example works fine, but that's because we are dealing with a very simple view:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view.
window = AnimatableWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
class AnimatableWindow: NSWindow {
var lastContentSize: CGSize = .zero
override func setContentSize(_ size: NSSize) {
if lastContentSize == size { return } // prevent multiple calls with the same size
lastContentSize = size
self.animator().setFrame(NSRect(origin: self.frame.origin, size: size), display: true, animate: true)
}
}
struct ContentView: View {
#State private var flag = false
var body: some View {
VStack {
Button("Change") {
self.flag.toggle()
}
}.frame(width: self.flag ? 100 : 300 , height: 200)
}
}

SwiftUI scroll view partially displays last item in Mac app

How do I properly setup a ScrollView with SwiftUI in a Mac app? The example below clips the last item.
import SwiftUI
struct NameRow: View {
var name: String
var body: some View {
VStack {
Spacer()
Text("\(name)")
Spacer()
Divider()
}.frame(width: 100, height: 40)
}
}
struct ContentView: View {
let names = ["Homer", "Marge", "Lisa", "Bart", "Maggie", "Krusty", "Burns", "Nelson", "Otto"]
var body: some View {
VStack {
ScrollView {
ForEach(names, id: \.self) { name in
NameRow(name: name)
}
}
}.frame(width: 100, height: 160)
}
}
As shown in the image, the last item is clipped by the window. This view is scrolled to the bottom but the "Otto" item does not fully display.
If you remove the .fullSizeContentView from the window initialization in AppDelegate.swift so it looks like the following, then it will fully display the list.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered, defer: false)
I don't know if this is a bug or by design.

Resources