肇鑫的技术博客

业精于勤,荒于嬉

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

Swift Packages Quick Learning

So far, I have learnt four ways of adding frameworks, manually, CocoaPods, Carthage and Swift Package Manager. This article is a summary of how to creating your own Swift Packages.

What is Swift Package?

A Swift Package is a bundle of source files with a meaningful name.

Platform

If unspecified, Swift Package works in all versions of Apple Operation Systems, this make a lot of version warnings. So a better idea is to provide the platform your package could work with.

// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "MyAppStore",
    platforms: [
        .macOS(.v11)
    ],

Dependencies

Adding dependencies is easy. However, you must added the dependencies to both targets and test test targets.

// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "MyAppStore",
    platforms: [
        .macOS(.v11)
    ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "MyAppStore",
            targets: ["MyAppStore"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "git@github.com:owenzhao/QRCodeKit.git", Package.Dependency.Requirement.branch("1.0.0")),
        .package(url: "git@github.com:weichsel/ZIPFoundation.git", "0.9.12"..<"1.0.0")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "MyAppStore",
            dependencies: ["QRCodeKit", "ZIPFoundation"]),
        .testTarget(
            name: "MyAppStoreTests",
            dependencies: ["MyAppStore", "QRCodeKit", "ZIPFoundation"]),
    ]
)

Versioning

For versioning, there is a trick. There is no where in the swift part to set the version of your package. The version is set by git tag. So you need to do the versioning in your git tool.

swift_package_versioning

Resources

Swift Packages can get some kinds of sources as resources automatically, but you may also need to add sources manually.

You can add them as files one by one. Or you can add the folder directly. If you use process, Xcode maybe take further optimization to the resources. If you want to keep them unchanged, you can use copy.

    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "MyAppStore",
            dependencies: ["QRCodeKit", "ZIPFoundation"],
            resources: [
                .process("icons"),
                .process("AllApps.zip")
            ]),

Localization

Add default localizedation in Package.swift

let package = Package(
    name: "MyAppStore",
    defaultLocalization: "en",
    platforms: [
        .macOS(.v11),
        .iOS(.v14)
    ],

You must use genstrings to get localized strings.

  1. Go to the directory of swift files.
  2. Create a directory named en.lpproj.
  3. Run code find ./ -name "*.m" -print0 | xargs -0 genstrings -o en.lproj.
  4. Copy en.lpproj as the name of you intent, say zh.lpproj.
  5. Translate the localized strings under zh.lpproj.
$ tree
.
├── AppInfoSwiftUIView.swift
├── MainSwiftUIView.swift
├── en.lproj
│   └── Localizable.strings
└── zh.lproj
      └── Localizable.strings

Using String resources in Swift Packages

Swift Package is built alone with the main bundle, it is called model Bundle, so you have to explicitly tell the bundle by name, or the system will try to find the string resources in the main bundle.

Button(action: download, label: {
    Text("Download", bundle: .module)
})

You should also notice that there are still some subtle issues. I found that the lang.lpproj directories must be under the target directory directly, or the preview won't take the environment effect.
However, running app goes fine whenever the directory goes.

References

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