肇鑫的技术博客

业精于勤,荒于嬉

Solved Issue: macOS USB Still Power On When System Is Set To Sleep

I encountered a nasty problem on my Mac. The keyboard backlight didn't turn off when the Mac was set to sleep. This issue happened before long times ago. At that time, I had no idea and had to reinstall the whole macOS. It would take hours. It was my last move. This time, I hope to find another way to solve this.

pmset -g

In terminal, using

pmset -g

This would list the power settings. And I showed that "sharingd" that prevent the system from sleep. That was a printed job that waiting for the printer. I had a wireless printer that was offline.

Opened the printer and print out the job and the issue fixed.

Final

The offline printing job stop the system from sleep. That was a service of system and not easy to find out.

Reference

Dealing with Filenames that contain "/" in macOS

My app crashed when I saved a file to disk. The reason was that the filename was "Poster 2/咕唧2.json". Although you can use "/" in a filename that in Finder. macOS uses "/" as the separator between directories and filenames. When the app ran, macOS tried to find a filename "咕唧2.json" under "Poster 2" directory, instead of finding a file named "Poster 2/咕唧2.json".

Finder uses ":" and Terminal uses "/"

You need to replace the "/" in filenames to ":". And Finder will read it show them as "/". So when writing, use

appInfos.forEach {
    let jsonData = getJsonData(from: $0)
    var filename = $0.name
    replaceSlashWithColon(&filename)
    let outputURL = URL(fileURLWithPath: filename + ".json", isDirectory: false, relativeTo: subFolder)
    try! jsonData.write(to: outputURL)
}
            
private func replaceSlashWithColon(_ str:inout String) {
    str = str.replacingOccurrences(of: "/", with: ":")
}

When reading:

appInfos = fileList.map({ name, version -> AppInfo in
    var filename = name
    replaceSlashWithColon(&filename)
    let url = URL(fileURLWithPath: filename + ".json", isDirectory: false, relativeTo: subfolder)
    let jsonData = try! Data(contentsOf: url)
    return try! decoder.decode(AppInfo.self, from: jsonData)
})

You only need to replace "/" to ":". You don't need to do the reverse. The system will take care of the rest.

References

Sidebar, Hybrid Programming SwiftUI with AppKit

You could get a NavigationView easily in SwiftUI. However, in macOS, there is an issue. If you collapse the sidebar and release your mouse, you cannot expand it again. What even worse, the state is also be remembered by the system and you could not get the sidebar even if you restart the app.

Here is a sample.

import SwiftUI

struct SidebarSwiftUIView: View {
    @State private var shouldShowPurple = false
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink(
                    destination: DestinationPageView(color: .purple),
                    isActive: $shouldShowPurple
                ) {
                    Text("Purple Page")
                }
                NavigationLink(
                    destination: DestinationPageView(color: .pink)
                ) {
                    Text("Pink Page")
                }
                NavigationLink(
                    destination: DestinationPageView(color: .orange)
                ) {
                    Text("Orange Page")
                }
            }
            .frame(minWidth: 180, idealWidth: 200, maxWidth: 250, alignment: .leading)
            .navigationTitle("Colors")
            Text("Select a color page from the links.")
        }
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
                shouldShowPurple = true
            }
        }
    }
}

struct SidebarSwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        SidebarSwiftUIView()
    }
}

struct DestinationPageView: View {
    var color: Color
    var body: some View {
        Text("Destination Page")
            .font(.title)
            .foregroundColor(color)
            .frame(minWidth: 300, idealWidth: 450, maxWidth:.infinity)
    }
}

It runs like this.

macOS_sidebar
The issue:

macOS_collapse_sidebar

Hybrid Programming With AppKit

Hybrid SwiftUI with AppKit is easy. It takes 2 steps.

NSHostingViewController

  1. Create a NSHostingViewController in Storyboard.
  2. Create HostingViewController.swift and init it with the SwiftUIView.
import Cocoa
import SwiftUI

class HostingController: NSHostingController<SidebarSwiftUIView> {
    @objc required dynamic init?(coder: NSCoder) {
        super.init(coder: coder, rootView: SidebarSwiftUIView())
    }
}

Sidebar

Unless in iOS/iPadOS, there is no sidebar for navigation bar in macOS. So you have to do it yourself. Just simply add a toolbar in WindowController.

add_toolbar_in_window_controller

Make sure to set the tool item as Navigational in Position.

leading_the_sidebar

Toggle the Sidebar

SwiftUI will create a NSSplitViewController automatically. So we use respond chain to collapse/expand the sidebar.

import Cocoa

@main
class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }

    @IBAction private func showSideBar(sender:Any?) {
        NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
    }
}

Bind the action to first responder.

action_binding

Final

Now the app will look like this.

final_sidebar_window

References

Collapse sidebar in SwiftUI (Xcode 12)