How to check app for compatibility with previous macOS versions? - swift

My app works well in macOS 10.14, but breaks in macOS 10.13. How do I debug it in Xcode, having macOS 10.14 installed? Or at least how do I know what can go wrong, maybe there is some static code check?
I guess the problem could be in this code:
private static func isSystemDarkModeEnabled() -> Bool {
let global = UserDefaults.standard.persistentDomain(forName: UserDefaults.globalDomain)
let style = global!["AppleInterfaceStyle"]
if style != nil && (style as! String).lowercased() == "dark" {
return true
}
return false
}
private class SystemDarkModeChangeObserver {
static func register() {
SystemDarkModeChangeObserver.shared = SystemDarkModeChangeObserver()
}
private static var shared: SystemDarkModeChangeObserver?
private init() {
DistributedNotificationCenter.default().addObserver(self, selector: #selector(self.onChange), name: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil)
}
#objc func onChange() {
// ...
}
}

You can change the deployment target in the general project settings. Xcode should tell you what you're using that is not available in that target.
Xcode defaults to target the version installed on your development machine.

Please delete Derived Data,
You can go to File > Workspace Settings if you are in a workspace environment or File > Project Settings for a regular project environment. Then click over the little grey arrow under Derived data section and select your project folder to delete it.
if pod are used than deintegrate and reinstalling all pod files again.
Hope this works for you!

Related

Cannot access Swift Package xcassets

I'm trying to use a color set from a xcassets folder that is inside a Swift Package (src). It doesn't seem to be working. I tested this out by writing a simple view that attempts to make use of the color:
Text("Hello")
.foregroundColor(Color("brandPrimary")
I got an empty view from this code; the text can't find the color.
I've done significant research around the web - reviewing WWDC videos such as Swift packages: Resources and localization, and they seem to suggest that xcassets folder are automatically included as resources. It doesn't work for me.
I tried to add process("Resources/Colors.xcassets") inside my package manifest, but that didn't help either.
From the Color documentation:
init(_ name: String, bundle: Bundle? = nil)
…
bundle
The bundle in which to search for the color resource. If you don’t indicate a bundle, the initializer looks in your app’s main bundle by default.
Your GoodPackage library's assets are not the app's main bundle, so you need to tell the Color initializer which bundle to search.
The Swift package manager's build process automatically creates a Bundle for each target/module that contains assets. Within the module, you can access that generated Bundle using the expression Bundle.module. SwiftPM actually writes Swift code to a file named resource_bundle_accessor.swift in your DerivedData to make this work. The generated source code looks like this:
import class Foundation.Bundle
import class Foundation.ProcessInfo
import struct Foundation.URL
private class BundleFinder {}
extension Foundation.Bundle {
/// Returns the resource bundle associated with the current Swift module.
static let module: Bundle = {
let bundleName = "YourPackageName_YourTargetName"
let overrides: [URL]
#if DEBUG
if let override = ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_URL"] {
overrides = [URL(fileURLWithPath: override)]
} else {
overrides = []
}
#else
overrides = []
#endif
let candidates = overrides + [
// Bundle should be present here when the package is linked into an App.
Bundle.main.resourceURL,
// Bundle should be present here when the package is linked into a framework.
Bundle(for: BundleFinder.self).resourceURL,
// For command-line tools.
Bundle.main.bundleURL,
]
for candidate in candidates {
let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
return bundle
}
}
fatalError("unable to find bundle named YourPackageName_YourTargetName")
}()
}
As you can see, that static let module property has (default) internal access, so you can only use it from source code within that module. By default, there is no way to access the module's Bundle from outside the module.
One solution is to add a public accessor for the module Bundle in the GoodPackage module. For example, add this to a source file in the GoodPackage module:
import Foundation
extension Bundle {
public var GoodPackage: Bundle { Bundle.module }
}
Then, in your app:
import GoodPackage
...
Text("Hello")
.foregroundColor(Color("brandPrimary", bundle: .GoodPackage))

Privileged file copy in macOS (Installing a helper binary to /usr/local/bin)

I have a helper binary mytool inside my main app bundle that I need to copy to /usr/local/bin.
Now bin might not always exist or have write access, so the standard NSWorkspace calls will fail on it. I looked into different ways to do this, but none are satisfactory (or I am doing it wrong)
Getting an authorization for replaceFile for NSWorkspace.requestAuthorization
This does not seem to work, as I still get a privileges error after trying to "replace" the file in /usr/local/bin/mytool with the one from my bundle.
Manually getting Authorization via AuthorizationCreate.
The problem here is that AuthorizationExecuteWithPrivileges is deprecated (or in my case not even available in Swift), and SMJobBless seems to be only for longer running helper processes. Also SMJobBlessrequires my helper tool to have an Info.plist of its own, which it doesn't have since its just a plain binary
So how do I manage to perform a privileged file copy in Swift?
PS: The app is not sandboxed, so NSOpenPanel does not help.
Well I dug out the deprecated API using dlsym, because there is simply no other way besides asking the user manually for his password, which I don't want to do unless the deprecated API disappears entirely.
So what I do now is authenticate a call to mytool --install using AuthorizationExecuteWithPrivileges like this:
import Foundation
import Security
public struct Sudo {
private typealias AuthorizationExecuteWithPrivilegesImpl = #convention(c) (
AuthorizationRef,
UnsafePointer<CChar>, // path
AuthorizationFlags,
UnsafePointer<UnsafeMutablePointer<CChar>?>, // args
UnsafeMutablePointer<UnsafeMutablePointer<FILE>>?
) -> OSStatus
/// This wraps the deprecated AuthorizationExecuteWithPrivileges
/// and makes it accessible by Swift
///
/// - Parameters:
/// - path: The executable path
/// - arguments: The executable arguments
/// - Returns: `errAuthorizationSuccess` or an error code
public static func run(path: String, arguments: [String]) -> Bool {
var authRef: AuthorizationRef!
var status = AuthorizationCreate(nil, nil, [], &authRef)
guard status == errAuthorizationSuccess else { return false }
defer { AuthorizationFree(authRef, [.destroyRights]) }
var item = kAuthorizationRightExecute.withCString { name in
AuthorizationItem(name: name, valueLength: 0, value: nil, flags: 0)
}
var rights = withUnsafeMutablePointer(to: &item) { ptr in
AuthorizationRights(count: 1, items: ptr)
}
status = AuthorizationCopyRights(authRef, &rights, nil, [.interactionAllowed, .preAuthorize, .extendRights], nil)
guard status == errAuthorizationSuccess else { return false }
status = executeWithPrivileges(authorization: authRef, path: path, arguments: arguments)
return status == errAuthorizationSuccess
}
private static func executeWithPrivileges(authorization: AuthorizationRef,
path: String,
arguments: [String]) -> OSStatus {
let RTLD_DEFAULT = dlopen(nil, RTLD_NOW)
guard let funcPtr = dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges") else { return -1 }
let args = arguments.map { strdup($0) }
defer { args.forEach { free($0) }}
let impl = unsafeBitCast(funcPtr, to: AuthorizationExecuteWithPrivilegesImpl.self)
return impl(authorization, path, [], args, nil)
}
}
If you want to do this using public APIs (meaning not using deprecated APIs, invoking Apple Script, shelling out via Process, etc.) then the only way to achieve this is using SMJobBless. For better or worse, that's the only option still officially supported by Apple.
If you want to install your binary in /usr/local/bin then that binary itself doesn't need to have an Info.plist. You'd want to create a different helper tool which would be installed via SMJobBless that could copy your binary to /usr/bin/local. It will be able to do that because a helper tool installed by SMJobBless always runs as root. Once you're done with all of this you could have the helper tool you installed with SMJobBless uninstall itself. No denying it's rather involved.
If you do want to go down this route, take a look at SwiftAuthorizationSample.

Content Previewer not loading on Xcode 12.5.1

I was working on a project the other day, and my preview stopped working. I got the error "Compiling Failed, cannot find "DataType" in scope" (all code/relationships will be shown below). I have restarted XCode a few times, and even commented out that area of code but it continues to not work, is anyone else having this problem and how can I fix it?
I am previewing a sheet called "Profile Sheet", just a standard swiftUI View, so the preview code looks like:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ProfileSheet()
}
}
In the profile sheet, it is accessing data from a static function associated with my model file (different struct, different file), the functions it is using, and the ones that define "DataType", the problematic generic are here:
static func saveData<DataType>(data: DataType, for key: String) {
defaults.setValue(data, forKey: key)
}
static func retrieveData<DataType>(defaultValue: DataType, for key: String) -> DataType {
guard let value = defaults.value(forKey: key) as? DataType else {return defaultValue}
return value
}
static func saveComplexData<DataType>(data: DataType, for key: String) where DataType: Codable {
let encodedData = try! JSONEncoder().encode(data)
defaults.setValue(encodedData, forKey: key)
}
static func retrieveComplexData<DataType>(defaultValue: DataType, for key: String) -> DataType where DataType: Codable {
guard let retrievedData = defaults.value(forKey: key) as? Data else { return defaultValue}
let decodedData = try! JSONDecoder().decode(DataType.self, from: retrievedData)
return decodedData
}
all of these function as intended, and obviously compile. Even when I run to launch the previewer and compiles my app does it builder properly, its only when it is then trying to launch the preview simulator.
Finally, I thought Id remove those functions entirely and just try to display a View that has no connection to them, so I previewed Text("test") and got the same error.
on Xcode 13 beta 3 & 4 I am able to run the simulator, but I cannot work only on those betas as they are still fairly unstable.
Any help is appreciated :)
I've seen quite a bit of weird behavior with frameworks, I think due to changes to the simulators to support Apple silicon. My temporary workaround is, in my app/extension targets, to add "arm64" to the Excluded Architectures build setting when building for the simulator (as your preview appears to be trying to do), and setting "Build Active Architecture Only" to No for all schemes. Might be worth a try.
As per triplette in apple developer answers.
Try to check it out
https://developer.apple.com/forums/thread/657913
I believe it's not an issue with your code it's an issue with Xcode due to apple silicon macs. try to do a software update and reinstall the Xcode.

swift framework: code changed and the built framework not update?

Environment: mac OS, Catalina, I tried on iOS simulator
AudioKit framework's structure is like this
I added the following code , rebuild the xcode target, and the change is not updated in the new framework
// see the right channel data
public class Table: NSObject, MutableCollection, Codable {
public
convenience init?(rhs file: AVAudioFile) {
let size = Int(file.length)
self.init(count: size)
guard let data = file.toFloatChannelData() else { return nil }
for i in 0 ..< size {
self[i] = data[1][i]
}
}
}
I have tried Product -> Clear Build folder , both on target AudioKit and Cookbook
I have tried Product -> Clear Build folder , and put alt, on both
I have tried to quit Xcode , and reboot MBP
Still not work.
How to update the swift framework.

Xcode 8.0 CBCentralManager Issue

I recently downloaded Xcode 8.0 and trying to run my previous project which uses core bluetooth.
I have enabled Use Legacy Swift Language Version in build setting for compatibility in swift 2.3
everything works, but one Issue occured,
func centralManagerDidUpdateState(central: CBCentralManager)
{
print("state is \(central.state.rawValue)")
if (central.state == CBCentralManagerState.PoweredOn)
{
self.centralManager?.scanForPeripheralsWithServices([serviceUUID], options: nil)
}
else
{
// do something like alert the user that ble is not on
}
}
previously central.state would return CBCentralManagerState type as int but now it returns CBManagerState so got an error so i changed to
if (central.state == CBManagerState.PoweredOn)
But CBManagerState is only supported in IOS 10+ but i want to build it for IOS 8.3+ so how can I change the code?
UPDATE
I also converted project to swift 3.0, but still same issue, so how can i run this project on mobiles with ios version below 10?
The simplest approach is just to use the short-hand reference to the enumeration value:
func centralManagerDidUpdateState(central: CBCentralManager)
{
print("state is \(central.state.rawValue)")
if (central.state == .PoweredOn)
{
self.centralManager?.scanForPeripheralsWithServices([serviceUUID], options: nil)
}
else
{
// do something like alert the user that ble is not on
}
}
Now your code will compile without errors or warnings and works correctly on all targets