How to disable ring type in TextField in SwiftUI

Issue #636

Normally we can just wrap NSTextField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct SearchTextField: NSViewRepresentable {
@Binding var text: String
var hint: String
var onCommit: (String) -> Void

func makeNSView(context: NSViewRepresentableContext<SearchTextField>) -> NSTextField {
let tf = NSTextField()
tf.focusRingType = .none
tf.isBordered = false
tf.isEditable = true
tf.isSelectable = true
tf.drawsBackground = false
tf.delegate = context.coordinator
tf.font = NSFont(name: OpenSans.bold.rawValue, size: 14)
tf.placeholderString = hint
return tf
}

func updateNSView(
_ nsView: NSTextField,
context: NSViewRepresentableContext<SearchTextField>
) {
nsView.font = NSFont(name: OpenSans.bold.rawValue, size: 14)
nsView.stringValue = text
}

func makeCoordinator() -> SearchTextField.Coordinator {
Coordinator(parent: self)
}

class Coordinator: NSObject, NSTextFieldDelegate {
let parent: SearchTextField
init(parent: SearchTextField) {
self.parent = parent
}

func controlTextDidChange(_ obj: Notification) {
let textField = obj.object as! NSTextField
parent.text = textField.stringValue
}

func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
self.parent.onCommit(textView.string)
return true
} else {
return false
}
}
}
}

But there is a weird Appstore rejection where the textfield is not focusable. The workaround is to use TextField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extension NSTextField {
open override var focusRingType: NSFocusRingType {
get { .none }
set { }
}
}

TextField(
"What's next?",
text: $text,
onCommit: { self.onAdd(self.text) }
)
.font(.system(size: 14, weight: .semibold, design: .rounded))
.textFieldStyle(PlainTextFieldStyle())
.padding(1)
.background(RoundedRectangle(cornerRadius: 2).stroke(Color.white))

How to handle enter key in NSTextField

Issue #635

1
textField.delegate = self
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
NSTextFieldDelegate

func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
// Do something against ENTER key
print("enter")
return true
} else if (commandSelector == #selector(NSResponder.deleteForward(_:))) {
// Do something against DELETE key
return true
} else if (commandSelector == #selector(NSResponder.deleteBackward(_:))) {
// Do something against BACKSPACE key
return true
} else if (commandSelector == #selector(NSResponder.insertTab(_:))) {
// Do something against TAB key
return true
} else if (commandSelector == #selector(NSResponder.cancelOperation(_:))) {
// Do something against ESCAPE key
return true
}

// return true if the action was handled; otherwise false
return false
}

How to toggle with animation in SwiftUI

Issue #632

Use Group

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private func makeHeader() -> some View {
Group {
if showsSearch {
SearchView(
onSearch: onSearch
)
.transition(.move(edge: .leading))
} else {
InputView(
onAdd: onAdd
)
.transition(.move(edge: .leading))
}
}
}

withAnimation {
self.showsSearch.toggle()
}

How to show context popover from SwiftUI for macOS

Issue #630

For SwiftUI app using NSPopover, to show context popover menu, we can ask for windows array, get the _NSPopoverWindow and calculate the position. Note that origin of macOS screen is bottom left

1
2
3
4
(lldb) po NSApp.windows
▿ 2 elements
- 0 : <NSStatusBarWindow: 0x101a02700>
- 1 : <_NSPopoverWindow: 0x101c01060>
1
2
3
4
5
6
7
8
9
10
let handler = MenuHandler()
handler.add(title: "About", action: onAbout)
handler.add(title: "Quit", action: onQuit)

guard let window = NSApp.windows.last else { return }
let position = CGPoint(
x: window.frame.maxX - 100,
y: window.frame.minY + 80
)
handler.menu.popUp(positioning: nil, at: position, in: nil)

How to make segmented control in SwiftUI for macOS

Issue #629

Use Picker with SegmentedPickerStyle.

1
2
3
4
5
6
7
8
9
10
11
12
Picker(selection: $preferenceManager.preference.display, label: EmptyView()) {
Image("grid")
.resizable()
.padding()
.tag(0)
Image("list")
.resizable()
.tag(1)
}.pickerStyle(SegmentedPickerStyle())
.frame(width: 50)
.padding(.leading, 16)
.padding(.trailing, 24)

Alternatively, we can make custom NSSegmentedControl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import AppKit
import SwiftUI

struct MySegmentControl: NSViewRepresentable {
func makeCoordinator() -> MySegmentControl.Coordinator {
Coordinator(parent: self)
}

func makeNSView(context: NSViewRepresentableContext<MySegmentControl>) -> NSSegmentedControl {
let control = NSSegmentedControl(
images: [
NSImage(named: NSImage.Name("grid"))!,
NSImage(named: NSImage.Name("list"))!
],
trackingMode: .selectOne,
target: context.coordinator,
action: #selector(Coordinator.onChange(_:))
)
return control
}

func updateNSView(_ nsView: NSSegmentedControl, context: NSViewRepresentableContext<MySegmentControl>) {

}

class Coordinator {
let parent: MySegmentControl
init(parent: MySegmentControl) {
self.parent = parent
}

@objc
func onChange(_ control: NSSegmentedControl) {

}
}
}

How to check if NSColor is light

Issue #627

Algorithm from https://www.w3.org/WAI/ER/WD-AERT/#color-contrast

1
2
3
4
5
6
7
8
9
10
11
extension NSColor {
var isLight: Bool {
guard
let components = cgColor.components,
components.count >= 3
else { return false }

let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
return brightness > 0.5
}
}

Then we can apply contrast color for our Text

1
2
3
4
5
6
7
8
9
10
extension Text {
func applyColorBaseOnBackground(_ color: NSColor?) -> some View {
guard let color = color else { return self }
if color.isMyLight {
return self.foregroundColor(Color.black)
} else {
return self
}
}
}

How to trigger onAppear in SwiftUI for macOS

Issue #626

SwiftUI does not trigger onAppear and onDisappear like we expect. We can use NSView to trigger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import SwiftUI

struct AppearAware: NSViewRepresentable {
var onAppear: () -> Void

func makeNSView(context: NSViewRepresentableContext<AppearAware>) -> AwareView {
let view = AwareView()
view.onAppear = onAppear
return view
}

func updateNSView(_ nsView: AwareView, context: NSViewRepresentableContext<AppearAware>) {

}
}

final class AwareView: NSView {
private var trigged: Bool = false
var onAppear: () -> Void = {}

override func viewDidMoveToSuperview() {
super.viewDidMoveToSuperview()

guard !trigged else { return }
trigged = true
onAppear()
}
}

Then we can use it as an hidden view, like in a ZStack

1
2
3
4
5
6
7
8
ZStack {
AppearAware(onAppear: {
LocalImageCache.shared.load(url: url) { image in
self.image = image
}
})
Image(image)
}

How to force refresh in ForEach in SwiftUI for macOS

Issue #625

For some strange reasons, content inside ForEach does not update with changes in Core Data NSManagedObject. The workaround is to introduce salt, like UUID just to make state change

1
2
3
4
5
6
7
8
9
10
struct NoteRow: View {
let note: Note
let id: UUID
}

List {
ForEach(notes) { note in
NoteRow(note: note, id: UUID())
}
}

How to access bookmark url in macOS

Issue #624

By default the approaches above grant you access while the app remains open. When you quit the app, any folder access you had is lost.

To gain persistent access to a folder even on subsequent launches, we’ll have to take advantage of a system called Security-Scoped Bookmarks.

Add entitlements

Use of app-scoped bookmarks and URLs

1
2
3
4
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>

Enabling Security-Scoped Bookmark and URL Access

If you want to provide your sandboxed app with persistent access to file system resources, you must enable security-scoped bookmark and URL access. Security-scoped bookmarks are available starting in macOS v10.7.3.

To add the bookmarks.app-scope or bookmarks.document-scope entitlement, edit the target’s .entitlements property list file using the Xcode property list editor. Use the entitlement keys shown in Table 4-4, depending on which type of access you want. Use a value of for each entitlement you want to enable. You can enable either or both entitlements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func saveBookmark(item: ShortcutItem) {
guard let url = item.fileUrl else { return }
do {
let bookmarkData = try url.bookmarkData(
options: .withSecurityScope,
includingResourceValuesForKeys: nil,
relativeTo: nil
)

item.bookmark = bookmarkData
} catch {
print("Failed to save bookmark data for \(url)", error)
}
}

func loadBookmark(item: ShortcutItem) -> URL? {
guard let data = item.bookmark else { return nil }
do {
var isStale = false
let url = try URL(
resolvingBookmarkData: data,
options: .withSecurityScope,
relativeTo: nil,
bookmarkDataIsStale: &isStale
)
if isStale {
saveBookmark(item: item)
}
return url
} catch {
print("Error resolving bookmark:", error)
return nil
}
}


_ = url.startAccessingSecurityScopedResource()
NSWorkspace.shared.open(url)
url.stopAccessingSecurityScopedResource()
1
2
3
4
5
6
_ = url.startAccessingSecurityScopedResource()
NSWorkspace.shared.selectFile(
url.path,
inFileViewerRootedAtPath: url.deletingLastPathComponent().path
)
url.stopAccessingSecurityScopedResource()

Read more

How to make TextField focus in SwiftUI for macOS

Issue #620

For NSWindow having levelother than .normal, need to override key and main property to allow TextField to be focusable

1
2
3
4
class FocusWindow: NSWindow {
override var canBecomeKey: Bool { true }
override var canBecomeMain: Bool { true }
}

Furthermore to customize TextField, consider using custom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import SwiftUI
import AppKit

struct MyTextField: NSViewRepresentable {
@Binding var text: String

func makeNSView(context: NSViewRepresentableContext<MyTextField>) -> NSTextField {
let tf = NSTextField()
tf.focusRingType = .none
tf.isBordered = false
tf.drawsBackground = false
tf.delegate = context.coordinator
return tf
}

func updateNSView(_ nsView: NSTextField, context: NSViewRepresentableContext<MyTextField>) {
nsView.stringValue = text
}

func makeCoordinator() -> MyTextField.Coordinator {
Coordinator(parent: self)
}

class Coordinator: NSObject, NSTextFieldDelegate {
let parent: MyTextField
init(parent: MyTextField) {
self.parent = parent
}

func controlTextDidChange(_ obj: Notification) {
let textField = obj.object as! NSTextField
parent.text = textField.stringValue
}
}
}

How to make tooltip in SwiftUI for macOS

Issue #617

Create empty NSView and use as overlay. Need to updateNSView in case we toggle the state of tooltip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import SwiftUI

struct Tooltip: NSViewRepresentable {
let tooltip: String

func makeNSView(context: NSViewRepresentableContext<Tooltip>) -> NSView {
let view = NSView()
view.toolTip = tooltip
return view
}

func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<Tooltip>) {
nsView.toolTip = tooltip
}
}
1
2
3
4
5
6
Button(action: self.onGear) {
Image("gear")
.styleButton()
}
.overlay(Tooltip(tooltip: "Settings"))
.buttonStyle(BorderlessButtonStyle())

Sometimes it’s better to add overlay tooltip to Image inside Button to avoid blocking

1
2
3
4
5
6
Button(action: self.onGear) {
Image("gear")
.styleButton()
.overlay(Tooltip(tooltip: "Settings"))
}
.buttonStyle(BorderlessButtonStyle())

How to present NSWindow modally

Issue #612

Use runModal

This method runs a modal event loop for the specified window synchronously. It displays the specified window, makes it key, starts the run loop, and processes events for that window. (You do not need to show the window yourself.) While the app is in that loop, it does not respond to any other events (including mouse, keyboard, or window-close events) unless they are associated with the window. It also does not perform any tasks (such as firing timers) that are not associated with the modal run loop. In other words, this method consumes only enough CPU time to process events and dispatch them to the action methods associated with the modal window.

Specify level in windowDidBecomeKey

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let controller = SettingsWindowController()
NSApp.runModal(for: controller.window!)

final class SettingsWindowController: NSWindowController, NSWindowDelegate {
init() {
let mainView = SettingsView()

let window = NSWindow(
contentRect: CGRect(x: 0, y: 0, width: 500, height: 300),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
let hosting = NSHostingView(rootView: mainView)
window.contentView = hosting

super.init(window: window)
window.delegate = self
}

func windowDidBecomeKey(_ notification: Notification) {
window?.level = .statusBar
}
func windowWillClose(_ notification: Notification) {
NSApp.stopModal()
}
}

How to use visual effect view in NSWindow

Issue #610

Set NSVisualEffectView as contentView of NSWindow, and our main view as subview of it. Remember to set frame or autoresizing mask as non-direct content view does not get full size as the window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let mainView = MainView()
.environment(\.managedObjectContext, coreDataManager.container.viewContext)

window = NSWindow(
contentRect: .zero,
styleMask: [.fullSizeContentView],
backing: .buffered,
defer: false
)
window.titlebarAppearsTransparent = true
window.center()
window.level = .statusBar
window.setFrameAutosaveName("MyApp")

let visualEffect = NSVisualEffectView()
visualEffect.blendingMode = .behindWindow
visualEffect.state = .active
visualEffect.material = .appearanceBased

let hosting = NSHostingView(rootView: mainView)
window.contentView = visualEffect
visualEffect.addSubview(hosting)
hosting.autoresizingMask = [.width, .height]

How to animate NSWindow

Issue #609

Use animator proxy and animate parameter

1
2
3
4
5
6
7
8
var rect = window.frame
rect.frame.origin.x = 1000
NSAnimationContext.runAnimationGroup({ context in
context.timingFunction = CAMediaTimingFunction(name: .easeIn)
window.animator().setFrame(rect, display: true, animate: true)
}, completionHandler: {

})

How to find active application in macOS

Issue #608

An NSRunningApplication instance for the current application.

1
NSRunningApplication.current

The running app instance for the app that receives key events.

1
NSWorkspace.shared.frontmostApplication

How to use application will terminate in macOS

Issue #601

On Xcode 11, applicationWillTerminate is not called because of default automatic termination on in Info.plist. Removing NSSupportsSuddenTermination to trigger will terminate notification

1
2
3
func applicationWillTerminate(_ notification: Notification) {
save()
}
1
2
3
4
<key>NSSupportsAutomaticTermination</key>
<true/>
<key>NSSupportsSuddenTermination</key>
<true/>

How to make borderless material NSTextField in SwiftUI for macOS

Issue #590

Use custom NSTextField as it is hard to customize TextFieldStyle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import SwiftUI

struct MaterialTextField: View {
let placeholder: String
@Binding var text: String
@State var isFocus: Bool = false

var body: some View {
VStack(alignment: .leading, spacing: 0) {
BorderlessTextField(placeholder: placeholder, text: $text, isFocus: $isFocus)
.frame(maxHeight: 40)
Rectangle()
.foregroundColor(isFocus ? R.color.separatorFocus : R.color.separator)
.frame(height: isFocus ? 2 : 1)
}
}
}

class FocusAwareTextField: NSTextField {
var onFocusChange: (Bool) -> Void = { _ in }

override func becomeFirstResponder() -> Bool {
let textView = window?.fieldEditor(true, for: nil) as? NSTextView
textView?.insertionPointColor = R.nsColor.action
onFocusChange(true)
return super.becomeFirstResponder()
}
}

struct BorderlessTextField: NSViewRepresentable {
let placeholder: String
@Binding var text: String
@Binding var isFocus: Bool

func makeCoordinator() -> Coordinator {
Coordinator(self)
}

func makeNSView(context: Context) -> NSTextField {
let textField = FocusAwareTextField()
textField.placeholderAttributedString = NSAttributedString(
string: placeholder,
attributes: [
NSAttributedString.Key.foregroundColor: R.nsColor.placeholder
]
)
textField.isBordered = false
textField.delegate = context.coordinator
textField.backgroundColor = NSColor.clear
textField.textColor = R.nsColor.text
textField.font = R.font.text
textField.focusRingType = .none
textField.onFocusChange = { isFocus in
self.isFocus = isFocus
}

return textField
}

func updateNSView(_ nsView: NSTextField, context: Context) {
nsView.stringValue = text
}

class Coordinator: NSObject, NSTextFieldDelegate {
let parent: BorderlessTextField

init(_ textField: BorderlessTextField) {
self.parent = textField
}

func controlTextDidEndEditing(_ obj: Notification) {
self.parent.isFocus = false
}

func controlTextDidChange(_ obj: Notification) {
guard let textField = obj.object as? NSTextField else { return }
self.parent.text = textField.stringValue
}
}
}

How to observe focus event of NSTextField in macOS

Issue #589

becomeFirstResponder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class FocusAwareTextField: NSTextField {
var onFocusChange: (Bool) -> Void = { _ in }

override func becomeFirstResponder() -> Bool {
let textView = window?.fieldEditor(true, for: nil) as? NSTextView
textView?.insertionPointColor = R.nsColor.action
onFocusChange(true)
return super.becomeFirstResponder()
}
}

textField.delegate // NSTextFieldDelegate
func controlTextDidEndEditing(_ obj: Notification) {
onFocusChange(false)
}

NSTextField and NSText

https://stackoverflow.com/questions/25692122/how-to-detect-when-nstextfield-has-the-focus-or-is-its-content-selected-cocoa

When you clicked on search field, search field become first responder once, but NSText will be prepared sometime somewhere later, and the focus will be moved to the NSText.

I found out that when NSText is prepared, it is set to self.currentEditor() . The problem is that when becomeFirstResponder()’s call, self.currentEditor() hasn’t set yet. So becomeFirstResponder() is not the method to detect it’s focus.

On the other hand, when focus is moved to NSText, text field’s resignFirstResponder() is called, and you know what? self.currentEditor() has set. So, this is the moment to tell it’s delegate that that text field got focused

Use NSTextView

Any time you want to customize NSTextField, use NSTextView instead

1
2
3
4
5
6
7
8
// NSTextViewDelegate
func textDidBeginEditing(_ notification: Notification) {
parent.isFocus = true
}

func textDidEndEditing(_ notification: Notification) {
parent.isFocus = false
}

How to change caret color of NSTextField in macOS

Issue #588

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FocusAwareTextField: NSTextField {
var onFocus: () -> Void = {}
var onUnfocus: () -> Void = {}

override func becomeFirstResponder() -> Bool {
onFocus()
let textView = window?.fieldEditor(true, for: nil) as? NSTextView
textView?.insertionPointColor = R.nsColor.action
return super.becomeFirstResponder()
}

override func resignFirstResponder() -> Bool {
onUnfocus()
return super.resignFirstResponder()
}
}

How to make TextView in SwiftUI for macOS

Issue #587

Use xib

Create a xib called ScrollableTextView, and drag just Scrollable text view as top object

Screenshot 2020-01-29 at 06 49 55

Connect just the textView property

1
2
3
4
5
import AppKit

class ScrollableTextView: NSScrollView {
@IBOutlet var textView: NSTextView!
}

Conform to NSViewRepresentable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import SwiftUI

struct TextView: NSViewRepresentable {
@Binding var text: String

func makeCoordinator() -> Coordinator {
Coordinator(self)
}

func makeNSView(context: Context) -> ScrollableTextView {
var views: NSArray?
Bundle.main.loadNibNamed("ScrollableTextView", owner: nil, topLevelObjects: &views)
let scrollableTextView = views!.compactMap({ $0 as? ScrollableTextView }).first!
scrollableTextView.textView.delegate = context.coordinator
return scrollableTextView
}

func updateNSView(_ nsView: ScrollableTextView, context: Context) {
guard nsView.textView.string != text else { return }
nsView.textView.string = text
}

class Coordinator: NSObject, NSTextViewDelegate {
let parent: TextView

init(_ textView: TextView) {
self.parent = textView
}

func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else { return }
self.parent.text = textView.string
}
}
}

There seems to be a bug that if we have open and close curly braces, any character typed into NSTextView will move the cursor to the end. This is easily fixed with a check in updateNSView

How to use Firebase Crashlytics in macOS app

Issue #585

New Firebase Crashlytics

Follow the new Firebase Crashlytics guide Get started with Firebase Crashlytics using the Firebase Crashlytics SDK

CocoaPods

Specify FirebaseCore for community managed macOS version of Firebase

1
2
3
4
5
6
7
8
9
10
platform :osx, '10.13'

target 'MyMacApp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!

pod 'FirebaseCore'
pod 'Firebase/Crashlytics'

end

Signing and capabilities

Under Hardware runtime, check Disable library validation
Under App sandbox, enable Outgoing connections (Client)

Run script

Add a new run script build phrase to the last

1
"${PODS_ROOT}/FirebaseCrashlytics/run"

In that build phase, under Input Files, specify dsym and info plist file for dsym to be recognized

1
2
$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}

AppDelegate

1
2
3
4
import FirebaseCore
import FirebaseCrashlytics

FirebaseApp.configure()

How to handle radio group for NSButton

Issue #579

Use same action, or we can roll our own implementation

An NSButton configured as a radio button (with the -buttonType set to NSRadioButton), will now operate in a radio button group for applications linked on 10.8 and later. To have the button work in a radio group, use the same -action for each NSButton instance, and have the same superview for each button. When these conditions are met, checking one button (by changing the -state to 1), will uncheck all other buttons (by setting their -state to 0).

1
2
3
4
5
6
7
import Omnia

@IBAction func onModeButtonTouch(_ sender: NSRadioButton) {
for button in [mode1Button, mode2Button] {
button?.isOn = button === sender
}
}

How to use Applications folder in macOS

Issue #573

There are 2 Applications folder

  • /System/Applications: contains Notes, Books, Calculator, …
  • /Applications: contains Safari, Xcode, Keynote, …

How to make Swift Package Manager package for multiple platforms

Issue #504

https://twitter.com/NeoNacho/status/1181245484867801088?s=20

There’s no way to have platform specific sources or targets today, so you’ll have to take a different approach. I would recommend wrapping all OS specific files in #if os and just having one target. For tests, you could do something similar, one test target, but conditional tests

Every files are in Sources folder, so we can use platform and version checks. For example Omnia is a Swift Package Manager that supports iOS, tvOS, watchOS, macOS and Catalyst.

For macOS only code, need to check for AppKit and Catalyst

https://github.com/onmyway133/Omnia/blob/master/Sources/macOS/ClickedCollectionView.swift

1
#if canImport(AppKit) && !targetEnvironment(macCatalyst)

For SwiftUI feature, need to check for iOS 13 and macOS 10.15

https://github.com/onmyway133/Omnia/blob/master/Sources/SwiftUI/Utils/ImageLoader.swift

1
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)

How to use Firebase in macOS

Issue #501

  • Use Catalyst
  • Add to CocoaPods
1
2
3
4
5
6
7
8
9
platform :ios, '13.0'

target 'MyApp' do
use_frameworks!

pod 'FirebaseCore'
pod 'Firebase/Firestore'

end

Troubleshooting

Select a team for gRPC-C++-gRPCCertificates-Cpp

Screenshot 2019-11-12 at 14 53 03

FIRAnalyticsConnector: building for Mac Catalyst, but linking in object file built for iOS Simulator

https://stackoverflow.com/questions/57666155/firanalyticsconnector-building-for-mac-catalyst-but-linking-in-object-file-bui

The problem was related to the difference between Firebase/Core and FirebaseCore. The first is a subspec of the Firebase pod that depends on FirebaseAnalytics. The second is only the FirebaseCore pod. Only the latter should be used for macOS.