How to make UserDefaults property wrapper

Issue #726

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@propertyWrapper
struct UserDefault<Value> {
let key: String
let defaultValue: Value
let container: UserDefaults = .standard

var wrappedValue: Value {
get {
return container.object(forKey: key) as? Value ?? defaultValue
}
set {
container.set(newValue, forKey: key)
}
}
}

Then we can use it as property and provide default value

1
2
3
final class KeepHistoryService {
@UserDefault(key: "keepHistoryCheckDate", defaultValue: nil)
var checkDate: Date?

How to use Set to check for bool in Swift

Issue #725

When you want to check for existence using Bool, consider using Set over Dictionary with Bool, as Set guarantee uniqueness. If using Dictionary instead, the value for key is Optional<Bool> where we have to check for both optional and true false within.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Book: Hashable {
let id: UUID
let name: String
}

let book1 = Book(id: UUID(), name: "1")
let book2 = Book(id: UUID(), name: "2")

func useDictionary() {
var hasChecked: [Book: Bool] = [:]
hasChecked[book1] = true
print(hasChecked[book1] == Optional<Bool>(true))
print(hasChecked[book2] == Optional<Bool>.none)
}

func useSet() {
var hasChecked: Set<Book> = Set()
hasChecked.insert(book1)
print(hasChecked.contains(book1))
print(hasChecked.contains(book2))
}

How to make visual effect blur in SwiftUI for macOS

Issue #724

We can use .blur modifier, but with VisualEffectView gives us more options for material and blending mode.

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
public struct VisualEffectView: NSViewRepresentable {
let material: NSVisualEffectView.Material
let blendingMode: NSVisualEffectView.BlendingMode

public init(
material: NSVisualEffectView.Material = .contentBackground,
blendingMode: NSVisualEffectView.BlendingMode = .withinWindow
) {
self.material = material
self.blendingMode = blendingMode
}

public func makeNSView(context: Context) -> NSVisualEffectView {
let visualEffectView = NSVisualEffectView()
visualEffectView.material = material
visualEffectView.blendingMode = blendingMode
visualEffectView.state = NSVisualEffectView.State.active
return visualEffectView
}

public func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context) {
visualEffectView.material = material
visualEffectView.blendingMode = blendingMode
}
}

How to make simple HUD in SwiftUI

Issue #723

Use @ViewBuilder to build dynamic content for our HUD. For blur effect, here I use NSVisualEffectView, but we can use .blur modifier also

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct HUD<Content>: View where Content: View {
let content: () -> Content

init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}

var body: some View {
content()
.frame(width: 80, height: 80)
.background(
VisualEffectView(material: .hudWindow)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(color: Color.black.opacity(0.22), radius: 12, x: 0, y: 5)
)
}
}

Then we can make some wrappers for information and progress HUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct HUD<Content>: View where Content: View {
let content: () -> Content

init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}

var body: some View {
content()
.frame(width: 80, height: 80)
.background(
VisualEffectView()
)
.cornerRadius(10)
.shadow(color: Color.gray.opacity(0.3), radius: 1, x: 0, y: 1)
}
}

Updated at 2020-12-26 20:59:10

How to instrument SwiftUI app

Issue #722

With Xcode 12, we can fire up Instrument to profile our app. Select SwiftUI template

Screenshot 2020-12-26 at 00 02 03

There are many profiles in that template, I find SwiftUI and Time Profile very useful. Here’s the profile I run for my app PastePal

SwiftUI View Body

This shows how many instance of View with body invocation are there, both for SwiftUI views and our app views

Taking a look at SwiftUI profile, it shows that ClipboardCell is taking most of the time, here over 7 seconds

Screenshot 2020-12-25 at 23 59 12

Time Profiler

This shows how much time was spent in each functions and call stack.

Then we drill down to Time Profiler, it shows that NSSharingService.submenu accounts for 75% of performance issue

Screenshot 2020-12-25 at 23 59 57

With these instruments, I found out that the NSSharingService context menu I added has poor performance.

This below method is used every time Menu is asked, which causes a significant overload on main thread, resulting in noticeable laggy scrolling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension NSSharingService {
static func submenu(text: String) -> some View {
return Menu(
content: {
ForEach(NSSharingService.sharingServices(forItems: [""])), id: \.title) { item in
Button(action: { item.perform(withItems: [string]) }) {
Image(nsImage: item.image)
Text(item.title)
}
}
},
label: {
Text("Share")
Image(systemName: SFSymbol.squareAndArrowUp.rawValue)
}
)
}
}

The reason is that NSSharingService.sharingServices requires quite some time to get sharing services. A quick fix is to cache the items using static variable. In Swift, static variable is like lazy attribute, it is computed only once on first asked

1
private static let items = NSSharingService.sharingServices(forItems: [""])

There are more to optimize in my case, for example calculation of image and dominant colors. But we should only optimize when seeing real performance issue and after proper instruments

Fix and instrument again

After the fix, the time reduces from over 7 seconds to just less than 200ms

Screenshot 2020-12-26 at 00 09 51 Screenshot 2020-12-26 at 00 09 01

Updated at 2020-12-30 06:37:53

How to force set frame explicitly for NSWindow

Issue #721

For setFrame to take effect

1
mainWindow.setFrame(rect, display: true)

we can remove auto save frame flag

1
mainWindow.setFrameAutosaveName("MyApp.MainWindow")

How to rotate NSStatusItem

Issue #720

NSStatusItem is backed by NSButton, we can animate this inner button. We need to specify position and anchorPoint for button’s layer so it rotates around its center point

1
2
3
4
5
6
7
8
9
10
11
12
guard
let button = statusItem.button
else { return }

let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.fromValue = 0
animation.toValue = CGFloat.pi * 2
animation.duration = 0.25
animation.repeatCount = 1
button.layer?.position = NSPoint(x: NSMidX(button.frame), y: NSMidY(button.frame))
button.layer?.anchorPoint = NSPoint(x: 0.5, y: 0.5)
button.layer?.add(animation, forKey: "rotate")

Updated at 2020-12-26 21:21:00

How to show image and text in menu item in SwiftUI for macOS

Issue #719

From SwiftUI 2 for macOS 11.0, we have access to Menu for creating menu and submenu. Usually we use Button for interactive menu items and Text for disabled menu items.

The easy way to customize menu with image is to call Menu with content and label. Pay attention to how we use Button and Label inside Content to create interactive menu items

1
2
3
4
5
6
7
8
9
10
11
12
13
Menu(
content: {
ForEach(collections) { collection in
Button(action: {) {
Label(collection.name, systemImage: SFSymbol.star.rawValue)
}
}
},
label: {
Image(systemName: SFSymbol.bookmarkFill.rawValue)
Text("Add to collection")
}
)

We can also use Image and Text separately. By default SwiftUI wraps these inside HStack automatically for us. For now, color has no effect in Menu, but it works on Text

1
2
3
4
Image(systemName: SFSymbol.bookmarkFill.rawValue)
.foregroundColor(Color.red)
Text(collection.name)
.foregroundColor(Color.green)

One way to mitigate this is to use Text with icon font. Here I use my FontAwesomeSwiftUI

There’s a problem that only the first Text is shown

1
2
3
4
Text(collection.icon)
.font(.awesome(style: .solid, size: 18))
.foregroundColor(Color.red)
Text(collection.name)

The solution is to concatenate Text. In SwiftUI, Text has + operator that allows us to make cool attributed texts

Screenshot 2020-12-23 at 07 34 40
1
2
3
4
Text(collection.icon)
.font(.awesome(style: .solid, size: 18))
.foregroundColor(Color.red)
+ Text(collection.name)

Updated at 2020-12-23 06:35:31

How to make sharing menu in SwiftUI for macOS

Issue #718

Use NSSharingService.sharingServices(forItems:) with an array of one empty string gives a list of sharing items. There we show image and title of each menu item.

We should cache sharing items as that can cause performance issue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import SwiftUI
import AppKit
import EasySwiftUI

extension NSSharingService {
private static let items = NSSharingService.sharingServices(forItems: [""])
static func submenu(text: String) -> some View {
return Menu(
content: {
ForEach(items, id: \.title) { item in
Button(action: { item.perform(withItems: [string]) }) {
Image(nsImage: item.image)
Text(item.title)
}
}
},
label: {
Text("Share")
Image(systemName: SFSymbol.squareAndArrowUp.rawValue)
}
)
}
}

Alternative, you can trigger NSSharingServicePicker from a button, it shows a context menu with sharing options

Read more


Updated at 2020-12-25 22:57:57

How to make stepper with plus and minus buttons in SwiftUI for macOS

Issue #717

Try to use predefined system colors in Human Interface Guidelines for macOS

Here we use this color unemphasizedSelectedTextBackgroundColor for button background

Screenshot 2020-12-21 at 06 24 16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
HStack(spacing: 1) {
makeUnderListButton(action: {}, icon: .plus)
makeUnderListButton(action: {}, icon: .minus)
}
.background(Color(NSColor.unemphasizedSelectedTextBackgroundColor))
.cornerRadius(4)

func makeUnderListButton(action: @escaping () -> Void, icon: AwesomeIcon) -> some View {
Button(action: action) {
Text(icon.rawValue)
.font(.awesome(style: .solid, size: 14))
}
.buttonStyle(HighlightButtonStyle(h: 8, v: 6, cornerRadius: 4))
}

Another thing is List, where we have selected and alternative background colors. We should also use dynamic system colors selectedContentBackgroundColor and alternatingContentBackgroundColors

1
2
3
4
5
6
7
8
9
10
11
12
13
VStack {
ForEach(apps.enumerated().map({ $0 }), id: \.element) { index, app in
makeRow(app: app)
.onTapGesture {
self.selected = app
}
.background(
self.selected == app
? Color(NSColor.selectedContentBackgroundColor)
: Color(NSColor.alternatingContentBackgroundColors[index % 2])
)
}
}

Read more


Updated at 2020-12-21 05:56:28

How to fix Picker not showing selection in SwiftUI

Issue #716

I have an enum that conforms to CaseIterable that I want to show in Picker

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Position: String, Codable, CaseIterable, Identifiable {
var id: String { rawValue }
case left
case right
case bottom
case top
}

Picker(selection: $preference.position, label: Text("Position")) {
ForEach(Preference.Position.allCases) { position in
Text(position.rawValue)
}
}

It compiles and runs just fine, but Picker does not show current selection regardless of any Picker style I choose. It does not update Binding at all.

The fix is to specify id, it looks redundant because of enum conforms to Identifiable, but it fixes the problem

1
2
3
4
5
Picker(selection: $preference.position, label: Text("Position")) {
ForEach(Preference.Position.allCases, id: \.self) { position in
Text(position.rawValue)
}
}

Mismatch between rawValue and enum case itself

Reading ForEach once again

1
init(_ data: Data, content: @escaping (Data.Element) -> Content)

Available when Data conforms to RandomAccessCollection, ID is Data.Element.ID, Content conforms to View, and Data.Element conforms to Identifiable.

So in our case, we use rawValue as id for Identifiable, so there’s mismatch between our selection being enum case and items in ForEach, which uses rawValue to uniquely identifies items. So our fix is to explicitly state that we want to use the enum case itself \.self as idfor ForEach

What we can also do is to declare enum case itself as id

1
2
3
4
5
6
7
enum Position: String, Codable, CaseIterable, Identifiable {
var id: Position { self }
case left
case right
case bottom
case top
}

The lesson learned here is we need to ensure the underlying type of selection in List and id used in ForEach are the same

Updated at 2020-12-23 06:05:08

My year in review 2020

Issue #715

I remember this time last year in December 2019, I spent almost every single bit of my free time on Puma because I want a Swift friendly version of fastlane that suits my need and leverages Swift 5 features.

Here’s my review of my work in year 2020.

Blogging

I started blogging on GitHub issue, starting from Issue 1 Hello world, again, now I have over 670 issues, which were generated into blog posts at my website https://onmyway133.com/

🍏 I used to use onmyway133.github.io domain, but then it feels right to have my own domain
🍎 I used to write a lot at Medium https://medium.com/@onmyway133 for many publications and my own Fantageek publication, I have got 2.3k followers with around 60k views per month
🍓 I list my most favorite articles, usually articles that I spent most time polishing here https://onmyway133.com/writing/

One of my very first articles published on Flawless iOS publication was A better way to update UICollectionView data in Swift with diff framework gets the highest traffic ever, and was rated most trending Swift article for 2018.

My one of few articles published in Medium in 2019 was How to make Auto Layout more convenient in iOS got featured in iOS Dev Weekly, and used to promote my library EasyAnchor

My big article this year is How to test push notifications in simulator and production iOS apps, which was also featured on iOS Dev Weekly, used to summary the changes in push notifications from iOS 7 to iOS 14, and to promote my push notification testing tool Push Hero

My blog at https://onmyway133.com/ has around 15k views each month. I can write proper, lengthy articles to get more views but I don’t want to. I want to write blog about solutions I have found, so that my future self can benefit from it without searching too much.

views

Open source

I have done quite a lot of open source, you can view here Open source. These have helped tons of apps, with 45k+ apps touched, and 3.4m+ downloads as stats on CocoaPods

My 4 libraries this year get inspired by SwiftUI and property wrappers

🍌 Spek leverages property wrapper to provide Spec syntax, similar to Quick, but simpler and can generate tests
🍈 Micro imitates SwiftUI State and ForEach syntax but use UICollectionView with diffable datasource, powered by my another library DeepDiff
🍑 EasySwiftUI contains many extensions and useful modifier that I use in my SwiftUI apps
🥝 FontAwesomeSwiftUI I was tired of using bitmap with dark and light variants, and I can’t use SFSymbols as I want to support macOS 10.15, so FontAwesome is a perfect choice. I couldn’t find library that has support for SwiftUI and easy to use with Swift Package Manager for iOS and macOS, so I made one

Besides, for all my libraries EasyStash, EasyAnchor, EasyTheme, EasyClosure, … I have now support Swift Package Manager, which is nicer to integrate. Thank you CocoaPods for all these years.

There are now 1.6k people following me on GitHub, that means a lot, meaning somehow my work is useful

Lately, I open source awesome-swiftui which I curate all SwiftUI resources, articles and libraries that I find useful for my apps

❤️ One day, I got sponsor from my dear friend Chris for my GitHub open source. Chris is my open source idol who ignited my desire for open source. No one loves open source more than Chris

Apps

I started making some apps late last year, first I published them on Gumroad, but it didn’t feel right, and then I published all my apps on AppStore. I like sandboxed apps from AppStore because they limit what the apps can do.

Apps without Twitter account and landing page seem off, so in May I started revamping my websites, and I wrote my apps page firstly with pure HTML and CSS, then I rewrote in React, because I like React and Javascript.

Notably, early 2020 I made Push Hero, PastePal then I made a complete overhaul lately with more features, thanks to all the feedback. I also took the time to revamp landing pages a lot, you can check landing pages for Push Hero for example because I have a white label solution

Learn how I did white label landing page using React

I have a lot of ideas, but very little time.

Learning

Coding can be done, but design is never finished. When making apps, I feel like I’m not certain at some design decisions and no matter how I landed with some designs, I didn’t feel happy. Then I took some design courses and books.

Below is my tweet that I share some design resources that I found useful

Listening

I used to listen to tech podcasts, then I was bored. Discussions like whether to use MVVM vs MVC, SwiftUI vs Catalyst, Swift vs Objective don’t interest me anymore.

If you ask me for choices, my default answer will be NO. Those who are super passionate about their idea will just ignore my advice and do it anyway.

Then I listen to indie and product development podcasts, it was inspiring. Once I got the mindset, they also became boring.
Now I listen to mostly books on Storytel, some books about habit and making time really make my days.

Tweet

I came back to Twitter early this year after quitting it for a while because I got enough of all negative and nonsense political debates. But then I found that I can decide who I can follow and what content I want to view. Then I started organizing List, making my own Chrome extensions to automate things and control what I want to view. I have followed quite many indie developers and great product people, I’ve learnt a lot. The downside is I’m overwhelmingly inspired, I can’t sleep.

I try to tweet more about what I’ve learned, sharing articles that I have written. For example here I share about how to gain product idea

WWDC Together

Notable this year is the website I make WWDC Together as a place for developers like us to hangout and watch videos together. Each video acts like its own chat room, you can also create private chatroom. My colleagues use this and we watched WWDC together with coke and pizza, it was a lot of fun.

I had the idea like 2 weeks before WWDC, so I had to make it quick. Working with React is fun to me, it is like playing video game.

I was lucky to be asked by John to write a guest post Behind the scenes of WWDC Together with Khoa Pham on his website.

It was also mentioned by Paul in his WWDC wrap up WWDC20: Wrap up and recommended talks

One area that absolutely flourished this year was community organization. Sites like https://wwdcwatch.party and https://wwdctogether.com took a huge amount of work to organize, but meant that people had the chance to have some interaction – had the chance to actually chat about WWDC and share their excitement. I’m really grateful to Michie, Khoa, and other community organizers for making this happen.

I also got to share about in in a well known Norwegian tech website orske WWDC Together lar oss se på Apples konferanse sammen

Speaking

In 2019, I made several meetup talks, and one pre conf talk for Mobile Era conf

In 2020, I’m lucky to be invited to talk in some events

🍅 WWDC Watch Party

I’m happy to be invited by Michie to talk along side with John and Łukasz, whom I really admire for their awesome open source contribution, to talk about how to start and contribute to open source

🥦 Bitrise webminar

I shared my thoughts about my favorites at WWDC and the future ahead

🍇 Contributing.today

During Hacktoberfest 2020, I was contacted by my friend Maxim to share about open source

Work

I’m happy to continue another awesome year at Shortcut and DNB, where I have awesome and super nice colleagues. I ‘ve made lots of friends who I can talk with, who invited me to play badminton, tennis, football, swimming, hiking. Why didn’t we meet earlier?

I was lucky to attend a workshop hosted by John Sundell at work, I learned a lot

Life

Thanks for a memorable year, despite all the lockdown. And remember, balance work and life. You don’t need to be super rich to be happy. Having lots of money in the bank while living alone is not fun at all.

Another year is coming to an end. When looking back, do you miss the time you didn’t spend with your friends and family, or do you miss the time you didn’t get to do your work?

May your code continue to compile 🙏

Updated at 2021-01-01 23:25:50

How to do didSet for State and Binding in SwiftUI

Issue #714

Below is an example of a parent ContentView with State and a child Sidebar with a Binding.

The didSet is only called for the property that is changed.

When we click Button in ContentView, that changes State property, so only the didSet in ContentView is called
When we click Button in Sidebar, that changes Binding property, so only the didSet in Sidebar is called

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
enum Tag: String {
case all
case settings
}

struct ContentView: View {
@State
private var tag: Tag = .all {
didSet {
print("ContentView \(tag)")
}
}

var body: some View {
Sidebar(tag: $tag)
Button(action: { tag = .settings }) {
Text("Button in ContentView")
}
}
}

struct Sidebar: View {
@Binding
var tag: Tag {
didSet {
print("Sidebar \(tag)")
}
}

var body: some View {
Text(tag.rawValue)
Button(action: { tag = .settings }) {
Text("Button in Sidebar")
}
}
}

Custom Binding with get set

Another way to observe Binding changes is to use custom Binding with get, set. Here even if we click Button in ContentView, the set block is triggered and here we can change State

1
2
3
4
5
6
7
8
9
10
11
12
var body: some View {
Sidebar(tag: Binding<Tag>(
get: { tag },
set: { newTag in
self.tag = newTag
print("ContentView newTag \(newTag)")
}
))
Button(action: { tag = .settings }) {
Text("Button in ContentView")
}
}

Convenient new Binding

We can also make convenient extension on Binding to return new Binding, with a hook allowing us to inspect newValue. So we can call like Sidebar(tag: $tag.didSet

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
extension Binding {
func didSet(_ didSet: @escaping (Value) -> Void) -> Binding<Value> {
Binding(
get: { wrappedValue },
set: { newValue in
self.wrappedValue = newValue
didSet(newValue)
}
)
}
}

struct ContentView: View {
@State
private var tag: Tag = .all {
didSet {
print("ContentView \(tag)")
}
}

var body: some View {
Sidebar(tag: $tag.didSet { newValue in
print("ContentView newTag \(newValue)")
})
Button(action: { tag = .settings }) {
Text("Button in ContentView")
}
}
}

Updated at 2021-02-06 21:57:28

How to add toolbar programatically in macOS

Issue #713

To setup toolbar, we need to implement NSToolbarDelegate that provides toolbar items. This delegate is responsible for many things

  • Set visible and allowed items with toolbarDefaultItemIdentifiers
  • Provide item with itemForItemIdentifier
  • Being notified with toolbarWillAddItem and toolbarDidRemoveItem
1
2
3
4
5
6
7
8
window.toolbarStyle = .unifiedCompact

let toolbar = NSToolbar(identifier: "Toolbar")
toolbar.displayMode = .iconAndLabel
toolbar.delegate = (NSApp.delegate as! AppDelegate)
toolbar.insertItem(withItemIdentifier: .add, at: 0)
toolbar.insertItem(withItemIdentifier: .settings, at: 1)
window.toolbar = toolbar
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
extension NSToolbarItem.Identifier {
static let add = NSToolbarItem.Identifier(rawValue: "Add")
static let settings = NSToolbarItem.Identifier(rawValue: "Settings")
}

extension AppDelegate: NSToolbarDelegate {
func toolbar(
_ toolbar: NSToolbar,
itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
willBeInsertedIntoToolbar flag: Bool
) -> NSToolbarItem? {
switch itemIdentifier {
case .add:
let item = NSToolbarItem(itemIdentifier: itemIdentifier)
item.label = "Add"
item.image = NSImage(named: NSImage.Name("add"))
let menuItem: NSMenuItem = NSMenuItem()
menuItem.submenu = nil
menuItem.title = "Add"
item.menuFormRepresentation = menuItem
item.toolTip = "Click here to add new entry"
return item
case .settings:
let item = NSToolbarItem(itemIdentifier: itemIdentifier)
item.label = "Settings"
let button = NSButton(image: NSImage(named: NSImage.Name("gear"))!, target: nil, action: nil)
button.bezelStyle = .texturedRounded
item.view = button
return item
default:
return nil
}
}

func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
[.add, .settings]
}

func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
[.add, .settings]
}
}

Read more

How to declare network Error with enum in Swift

Issue #712

Describe all possible paths in your program with enum. This is great to track down bugs and to not miss representing potential cases in UI. Errors can come from app layer, backend layer to network issues.

Enum is handy in both UIKit and SwiftUI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum NetworkError: Swift.Error {
enum RequestError {
case invalidRequest(URLRequest)
case encodingError(Swift.EncodingError)
case other(NSError)
}

enum ServerError {
case decodingError(Swift.DecodingError)
case noInternetConnection
case timeout
case internalServerError
case other(statusCode: Int, response: HTTPURLResponse)
}

case requestError(RequestError)
case serverError(ServerError)
}

and note that Foundation has NSError with a bunch of well defined error code ready to use. We can use this before introduce our custom error cases

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public var NSURLErrorUnknown: Int { get }
public var NSURLErrorCancelled: Int { get }
public var NSURLErrorBadURL: Int { get }
public var NSURLErrorTimedOut: Int { get }
public var NSURLErrorUnsupportedURL: Int { get }
public var NSURLErrorCannotFindHost: Int { get }
public var NSURLErrorCannotConnectToHost: Int { get }
public var NSURLErrorNetworkConnectionLost: Int { get }
public var NSURLErrorDNSLookupFailed: Int { get }
public var NSURLErrorHTTPTooManyRedirects: Int { get }
public var NSURLErrorResourceUnavailable: Int { get }
public var NSURLErrorNotConnectedToInternet: Int { get }
public var NSURLErrorRedirectToNonExistentLocation: Int { get }
public var NSURLErrorBadServerResponse: Int { get }
public var NSURLErrorUserCancelledAuthentication: Int { get }
public var NSURLErrorUserAuthenticationRequired: Int { get }
public var NSURLErrorZeroByteResource: Int { get }
public var NSURLErrorCannotDecodeRawData: Int { get }
public var NSURLErrorCannotDecodeContentData: Int { get }
public var NSURLErrorCannotParseResponse: Int { get }

How to programatically select row in List in SwiftUI

Issue #711

List has a selection parameter where we can pass selection binding. As we can see here selection is of type optional Binding<Set<SelectionValue>>? where SelectionValue is any thing conforming to Hasable

1
2
3
4
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public struct List<SelectionValue, Content> : View where SelectionValue : Hashable, Content : View {
@available(watchOS, unavailable)
public init(selection: Binding<Set<SelectionValue>>?, @ViewBuilder content: () -> Content)

So we can programatically control selection by tagging row with our own Tag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct SideView: View {
enum Tag: String, Hashable {
case all
case settings
}

@State
var selection: Tag? = .all

var body: some View {
List {
all
.tag(Tag.all)
categories
settings
.tag(Tag.settings)
}
}
}

How to show sidebar in SwiftUI for macOS

Issue #710

Starting from macOS 11, we can use List with SidebarListStyle inside NavigationView to declare master detail view. The SidebarListStyle makes list translucent. It automatically handles selection and marks selected row in list with accent color.

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
struct MainView: some View {
var body: some View {
NavigationView {
sidebar
ContentView()
}
}

private var sidebar: some View {
List {
Group {
Text("Categories")
.foregroundColor(.gray)
ForEach(categories) { category in
NavigationLink(destination: ContentView(category: category)) {
Label(category.name, systemImage: "message")
}
}
}

Divider()
NavigationLink(destination: SettingsView()) {
Label("Settings", systemImage: "gear")
}
}
.listStyle(SidebarListStyle())
}
}

If we use Section instead of just Group we get a nice dropdown arrow button to expand and collapse section

1
2
3
4
5
List {
Section(header: Text("Categories")) {
ForEach
}
}

Show and hide side bar

To toggle side bar, we can use toggleSidebar selector since for now, sidebar is backed by NSSplitViewController

1
mainWindow.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)

We can specify tool bar items on either sidebar or content.

1
2
3
4
5
6
7
8
.toolbar{
//Toggle Sidebar Button
ToolbarItem(placement: .navigation){
Button(action: toggleSidebar) {
Image(systemName: "sidebar.left")
})
}
}

For tool bar to work, we must use App and embed views inside WindowGroup

1
2
3
4
5
6
7
8
@main
struct AppWithSidebarAndToolbar: App {
var body: some Scene {
WindowGroup {
MainView()
}
}
}

Updated at 2021-01-06 20:43:40

How to mock UNNotificationResponse in unit tests

Issue #708

The best way to test is to not have to mock at all. The second best way is to have your own abstraction over the things you would like to test, either it is in form of protocol or some function injection.

But in case you want a quick way to test things, and want to test as real as possible, then for some cases we can be creative to mock the real objects.

One practical example is when we have some logic to handle notification, either showing or deep link user to certain screen. From iOS 10, notifications are to be delivered via UNUserNotificationCenterDelegate

1
2
3
4
5
@available(iOS 10.0, *)
public protocol UNUserNotificationCenterDelegate : NSObjectProtocol {
@available(iOS 10.0, *)
optional func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void)
}

and all we get is UNNotificationResponse which has no real way to construct it.

1
2
3
4
5
6
@available(iOS 10.0, *)
open class UNNotificationResponse : NSObject, NSCopying, NSSecureCoding {


// The notification to which the user responded.
@NSCopying open var notification: UNNotification { get }

That class inherits from NSCopying which means it is constructed from NSCoder, but how do we init it?

1
let response = UNNotificationResponse(coder: ???)

NSObject and NSCoder

The trick is, since UNNotificationResponse is NSObject subclass, it is key value compliant, and since it is also NSCopying compliant, we can make a mock coder to construct it

1
2
3
4
private final class KeyedArchiver: NSKeyedArchiver {
override func decodeObject(forKey _: String) -> Any { "" }
override func decodeInt64(forKey key: String) -> Int64 { 0 }
}

On iOS 12, we need to add decodeInt64 method, otherwise UNNotificationResponse init fails. This is not needed on iOS 14

UNNotificationResponse has a read only UNNotification, which has a readonly UNNotificationRequest, which can be constructed from a UNNotificationContent

Luckily UNNotificationContent has a counterpart UNMutableNotificationContent

Now we can make a simple extension on UNNotificationResponse to quickly create that object in tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private extension UNNotificationResponse {
static func with(
userInfo: [AnyHashable: Any],
actionIdentifier: String = UNNotificationDefaultActionIdentifier
) throws -> UNNotificationResponse {
let content = UNMutableNotificationContent()
content.userInfo = userInfo
let request = UNNotificationRequest(
identifier: "",
content: content,
trigger: nil
)

let notification = try XCTUnwrap(UNNotification(coder: KeyedArchiver()))
notification.setValue(request, forKey: "request")

let response = try XCTUnwrap(UNNotificationResponse(coder: KeyedArchiver()))
response.setValue(notification, forKey: "notification")
response.setValue(actionIdentifier, forKey: "actionIdentifier")
return response
}
}

We can then test like normal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func testResponse() throws {
let data: [AnyHashable: Any] = [
"data": [
"type": "OPEN_ARTICLE",
"articleId": "1",
"articleType": "Fiction",
"articleTag": "1"
]
]
let response = try UNNotificationResponse.with(userInfo: data)
let centerDelegate = ArticleCenterDelegate()
centerDelegate.userNotificationCenter(
UNUserNotificationCenter.current(),
didReceive: response,
withCompletionHandler: {}
)
XCTAssertEqual(response.notification.request.content.userInfo["type"], "OPEN_ARTICLE")
XCTAssertEqual(centerDelegate.didOpenArticle, true)
}

decodeObject for key

Another way is to build a proper KeyedArchiver that checks key and return correct property. Note that we can reuse the same NSKeyedArchiver to nested properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private final class KeyedArchiver: NSKeyedArchiver {
let request: UNNotificationRequest
let actionIdentifier: String
let notification: UNNotification

override func decodeObject(forKey key: String) -> Any? {
switch key {
case "request":
return request
case "actionIdentifier":
return actionIdentifier
case "notification":
return UNNotification(coder: self)
default:
return nil
}
}
}

Updated at 2020-12-07 11:49:55

How to support right click menu to NSStatusItem

Issue #707

The trick is to set the button oinside of statusItem to send actions on both leftMouseUp and rightMouseUp.

Another thing to note is we use popUpMenu on NSStatusItem, although it is marked as deprecated on macOS 10.14. We can set menu but that overrides left click.

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
import Omnia

private let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
private let statusItemMenuHandler = MenuHandler()

func setupStatusMenu() {
if let button = statusItem.button {
button.image = NSImage(named: NSImage.Name("statusMenuIcon"))
button.contentTintColor = NSColor.black
button.action = #selector(statusMenuButtonTouched(_:))
button.sendAction(on: [.leftMouseUp, .rightMouseUp]) // This is important

statusItemMenuHandler.add(title: "About", action: {
NSWorkspace.shared.open(URL(string: "https://onmyway133.com/pushhero")!)
})
}
}

@objc
private func statusMenuButtonTouched(_ sender: NSStatusBarButton) {
guard let event = NSApp.currentEvent else { return }
switch event.type {
case .rightMouseUp:
statusItem.popUpMenu(statusItemMenuHandler.menu)
// statusItem.menu = statusItemMenuHandler.menu // this overrides left click
default:
popover.toggle()
}
}

Updated at 2020-12-08 05:11:24

How to convert struct to Core Data NSManagedObject

Issue #706

Use Mirror and set key value as NSManagedObject subclasses from NSObject

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
import CoreData

final class ManagedObjectConverter {
func convert<M>(m: M, context: NSManagedObjectContext) throws -> NSManagedObject {
let entityName = String(describing: m)
guard let entityDescription = NSEntityDescription.entity(
forEntityName: entityName,
in: context
) else {
throw AppError.parsing
}

let managedObject = NSManagedObject(
entity: entityDescription,
insertInto: context
)

let mirror = Mirror(reflecting: m)

guard mirror.displayStyle == .struct else {
throw AppError.parsing
}

for case let (label?, anyValue) in mirror.children {
managedObject.setValue(anyValue, forKey: label)
}

return managedObject
}
}

Updated at 2020-12-07 06:05:04

How to format ISO date string in Javascript

Issue #705

Supposed we have date in format ISO8601 and we want to get rid of T and millisecond and timezone Z

1
2
3
4
const date = new Date()
date.toDateString() // "Sat Dec 05 2020"
date.toString() // "Sat Dec 05 2020 06:58:19 GMT+0100 (Central European Standard Time)"
date.toISOString() // "2020-12-05T05:58:19.081Z"

We can use toISOString, then split base on the dot . then remove character T

1
2
3
4
date
.toISOString()
.split('.')[0]
.replace('T', ' ')

How to declare Error in Swift

Issue #704

We used to declare enum that conforms to Error, but any type like struct or class can conform to Error as well.

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
enum NetworkError: Error {
case failToCreateRequest
case failToParseResponse
case failToReachServe
}

struct DetailError: Error {
let networkError: Error
let createdAt: Date
let tag: String
}

final class TrackError: Error {
let trackId: String
let detailError: DetailError
let trackSession: String

init(trackId: String, detailError: DetailError, trackSession: String) {
self.trackId = trackId
self.detailError = detailError
self.trackSession = trackSession
}
}

let networkError = NetworkError.failToCreateRequest
let detailError = DetailError(networkError: networkError, createdAt: Date(), tag: "0.1")
let trackError = TrackError(trackId: "AB-01", detailError: detailError, trackSession: "101")

How to convert from paid to free with IAP

Issue #703

What is receipt

Read When to refresh a receipt vs restore purchases in iOS?

From iOS 7, every app downloaded from the store has a receipt (for downloading/buying the app) at appStoreReceiptURL. When users purchases something via In App Purchase, the content at appStoreReceiptURL is updated with purchases information. Most of the cases, you just need to refresh the receipt (at appStoreReceiptURL) so that you know which transactions users have made.

Note

  • Receipt is generated and bundled with your app when user download the app, whether it is free or paid
  • When user makes IAP, receipt is updated with IAP information
  • When user downloads an app (download free, or purchase paid app), they get future updates (whether free or paid) forever.
  • Call SKReceiptRefreshRequest or SKPaymentQueue.restoreCompletedTransactions asks for Appstore credential
  • When we build the app from Xcode or download from Testflight, receipt is not bundled within the app since the app is not downloaded from AppStore. We can use SKReceiptRefreshRequest to download receipt from sandbox Appstore
  • restoreCompletedTransactions updates app receipt
  • Receipt is stored locally on device, so when user uninstalls and reinstalls your app, there’s no in app purchases information, this is when you should refresh receipt or restoreCompletedTransactions

Users restore transactions to maintain access to content they’ve already purchased. For example, when they upgrade to a new phone, they don’t lose all of the items they purchased on the old phone. Include some mechanism in your app to let the user restore their purchases, such as a Restore Purchases button. Restoring purchases prompts for the user’s App Store credentials, which interrupts the flow of your app: because of this, don’t automatically restore purchases, especially not every time your app is launched.

In most cases, all your app needs to do is refresh its receipt and deliver the products in its receipt. The refreshed receipt contains a record of the user’s purchases in this app, on this device or any other device. However, some apps need to take an alternate approach for one of the following reasons:

  • If you use Apple-hosted content, restoring completed transactions gives your app the transaction objects it uses to download the content. If you need to support versions of iOS earlier than iOS 7, where the app receipt isn’t available, restore completed transactions instead.
  • Refreshing the receipt asks the App Store for the latest copy of the receipt. Refreshing a receipt does not create any new transactions.
  • Restoring completed transactions creates a new transaction for every completed transaction the user made, essentially replaying history for your transaction queue observer.

More about receipt, from WWDC 2017, What’s new in StoreKit session https://developer.apple.com/videos/play/wwdc2017/303/

receipt

You can also watch WWDC 2017, session Advanced StoreKit for more detail https://developer.apple.com/videos/play/wwdc2017/305/

receipt tips

Restoring Purchased Products

Read Restoring Purchased Products

Users sometimes need to restore purchased content, such as when they upgrade to a new phone.

Don’t automatically restore purchases, especially when your app is launched. Restoring purchases prompts for the user’s App Store credentials, which interrupts the flow of your app

In most cases, you only need to refresh the app receipt and deliver the products listed on the receipt. The refreshed receipt contains a record of the user’s purchases in this app, from any device the user’s App Store account is logged into

Refreshing a receipt doesn’t create new transactions; it requests the latest copy of the receipt from the App Store

Restoring completed transactions creates a new transaction for every transaction previously completed, essentially replaying history for your transaction queue observer. Your app maintains its own state to keep track of why it’s restoring completed transactions and how to handle them.

What are the different IAP types

From AppStore

Consumable (pay everytime)

A consumable In-App Purchase must be purchased every time the user downloads it. One-time services, such as fish food in a fishing app, are usually implemented as consumables.

Non-Consumable (one time payment)

Non-consumable In-App Purchases only need to be purchased once by users. Services that do not expire or decrease with use are usually implemented as non-consumables, such as new race tracks for a game app.

Auto-Renewable Subscriptions (will deduct money from your credit card on a cycle complete)

Auto-renewable Subscriptions allow the user to purchase updating and dynamic content for a set duration of time. Subscriptions renew automatically unless the user opts out, such as magazine subscriptions.

Free Subscription (no payment and is still visible even you did not submitted your account detail to itunes connect)

Free subscriptions are a way for developers to put free subscription content in Newsstand. Once a user signs up for a free subscription, it will be available on all devices associated with the user’s Apple ID. Note that free subscriptions do not expire and can only be offered in Newsstand-enabled apps.

Non-Renewing (need to renew manually)

Subscription Non-Renewing Subscriptions allow the sale of services with a limited duration. Non-Renewing Subscriptions must be used for In-App Purchases that offer time-based access to static content. Examples include a one week subscription to voice guidance feature within a navigation app or an annual subscription to online catalog of archived video or audio.

When is app receipt missing

Read SKReceiptRefreshRequest

Use this API to request a new receipt if the receipt is invalid or missing

Receipt is stored locally on device. It can be missing in case user sync or restore device.

Watch WWDC 2014 - 305 Preventing Unauthorized Purchases with Receipts

How to check receipt existence

1
2
Bundle.main.appStoreReceiptURL
checkResourceIsReachable

How to read receipt

Read In-App Purchases: Receipt Validation Tutorial

The receipt consists of a single file in the app bundle. The file is in a format called PKCS #7. The payload consists of a set of receipt attributes in a cross-platform format called ASN.1

1
2
3
4
5
6
7
8
9
10
case 12: // Receipt Creation Date
var dateStartPtr = ptr
receiptCreationDate = readASN1Date(ptr: &dateStartPtr, maxLength: length)

case 17: // IAP Receipt
print("IAP Receipt.")

case 19: // Original App Version
var stringStartPtr = ptr
originalAppVersion = readASN1String(ptr: &stringStartPtr, maxLength: length)

Use TPInAppReceipt which includes certificates.

1
try InAppReceipt.localReceipt()

Check Receipt Fields

1
2
3
4
Original Application Version
The version of the app that was originally purchased.
ASN.1 Field Type 19
ASN.1 Field Value UTF8STRING

Note

This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in macOS) in the Info.plist file when the purchase was originally made

CFBundleVersion is build number, and CFBundleShortVersionString is app version

1
2
3
In-App Purchase Receipt
The receipt for an in-app purchase.
ASN.1 Field Type 17

Read Validating Receipts with the App Store

Sample verifyReceipt json

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
{
"receipt": {
"receipt_type": "ProductionSandbox",
"adam_id": 0,
"app_item_id": 0,
"bundle_id": "com.example.app.ios",
"application_version": "3",
"download_id": 0,
"version_external_identifier": 0,
"receipt_creation_date": "2018-11-13 16:46:31 Etc/GMT",
"receipt_creation_date_ms": "1542127591000",
"receipt_creation_date_pst": "2018-11-13 08:46:31 America/Los_Angeles",
"request_date": "2018-11-13 17:10:31 Etc/GMT",
"request_date_ms": "1542129031280",
"request_date_pst": "2018-11-13 09:10:31 America/Los_Angeles",
"original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
"original_purchase_date_ms": "1375340400000",
"original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
"original_application_version": "1.0",
"in_app": [{
"quantity": "1",
"product_id": "test2",
"transaction_id": "1000000472106082",
"original_transaction_id": "1000000472106082",
"purchase_date": "2018-11-13 16:46:31 Etc/GMT",
"purchase_date_ms": "1542127591000",
"purchase_date_pst": "2018-11-13 08:46:31 America/Los_Angeles",
"original_purchase_date": "2018-11-13 16:46:31 Etc/GMT",
"original_purchase_date_ms": "1542127591000",
"original_purchase_date_pst": "2018-11-13 08:46:31 America/Los_Angeles",
"is_trial_period": "false"
}]
},
"status": 0,
"environment": "Sandbox"
}

Verify your receipt first with the production URL; then verify with the sandbox URL if you receive a 21007 status code. This approach ensures you do not have to switch between URLs while your application is tested, reviewed by App Review, or live in the App Store.

Show me the code

Let’s use enum to represent possible states for each resource. Here’s simple case where we only have 1 non consumable IAP product.

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
enum IAPError: Error {
case failedToRefreshReceipt
case failedToRequestProduct
case failedToPurchase
case receiptNotFound
}

enum IAPResourceState<T> {
case notAsked
case loading
case success(T)
case failure(IAPError)
}

final class PricingPlan: ObservableObject {
static let pro = (Bundle.main.bundleIdentifier ?? "") + ".pro"

@Published
var isPro: Bool = false
@Published
var product: IAPResourceState<SKProduct> = .notAsked
@Published
var purchase: IAPResourceState<SKPayment> = .notAsked
@Published
var receipt: IAPResourceState<InAppReceipt> = .notAsked
}

Let’s have a central place for managing all IAP operations, called IAPManager, it can update our ObservableObject PricingPlan hence triggers update to SwiftUI.

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
import StoreKit
import TPInAppReceipt
import Version

final class IAPManager: NSObject {
private let pricingPlan: PricingPlan
private let paymentQueue: SKPaymentQueue

init(pricingPlan: PricingPlan) {
self.pricingPlan = pricingPlan
self.paymentQueue = SKPaymentQueue.default()

super.init()

self.paymentQueue.add(self)
}

func requestProducts() {
let identifiers = PricingPlan.pro
let request = SKProductsRequest(productIdentifiers: Set(arrayLiteral: identifiers))
request.delegate = self
pricingPlan.product = .loading
request.start()
}

func purchase(product: SKProduct) {
guard SKPaymentQueue.canMakePayments() else {
showAlert(text: "You are not allowed to make payment. Please check device settings.")
return
}

pricingPlan.purchase = .loading
let payment = SKPayment(product: product)
paymentQueue.add(payment)
}

func refreshReceipt() {
let request = SKReceiptRefreshRequest()
request.delegate = self
request.start()
}

func restorePurchase() {
paymentQueue.restoreCompletedTransactions()
}
}

Refresh receipt

You can use restoreCompletedTransactions if you simply finishTransaction and grant user pro feature, like in this simple tutorial In-App Purchase Tutorial: Getting Started, search for SKPaymentTransactionObserver. restoreCompletedTransactions also updates receipt.

Otherwise refreshing receipt is a better idea. It serves both case when receipt is not there locally and when you want to restore transactions. With receipt refreshing, no restored transactions are created and SKPaymentTransactionObserver is not called, so we need to check receipt proactively.

Either restoreCompletedTransactions or SKReceiptRefreshRequest asks for AppStore credential so you should present a button there and ask user.

Check local receipt

Try to locate local receipt and examine it.

  • If it is not there (missing, corrupted), refresh receipt
  • If it’s there, check if it was from a version when the app was still as paid. Notice the difference in meaning of originalAppVersion in macOS and iOS
  • If it is not paid, check if this receipt contains In App Purchase information for our product

In practice, we need to perform some basic checks on receipt, like bundle id, app version, device id. Read In-App Purchases: Receipt Validation Tutorial, search for Validating the Receipt. TPInAppReceipt also has some handy verify functions

Besides verifying receipt locally, it is advisable to call verifyreceipt either on device, or better on serve to let Apple verify receipt and returns you a human readable json for receipt information.

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
func checkReceipt() {
DispatchQueue.main.async {
do {
let receipt = try InAppReceipt.localReceipt()
self.pricingPlan.receipt = .success(receipt)
if self.isPaid(receipt: receipt) {
self.pricingPlan.isPro = true
} else if receipt.containsPurchase(ofProductIdentifier: PricingPlan.pro) {
self.pricingPlan.isPro = true
}
} catch {
self.pricingPlan.receipt = .failure(.receiptNotFound)
}
}
}

private func isPaid(receipt: InAppReceipt) -> Bool {
#if os(macOS)
// originalAppVersion is CFBundleShortVersionString
if let version = Version(receipt.originalAppVersion) {
return version < versionToIAP
}
#else
// originalAppVersion is CFBundleVersion
if let buildNumber = Int(receipt.originalAppVersion) {
return buildNumber < buildNumberToIAP
}
#endif
return false
}

Finally, observe SKProductsRequestDelegate which also conforms to SKRequestDelegate for both product and receipt refresh request

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
extension IAPManager: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
DispatchQueue.main.async {
guard let product = response.products.first else {
self.pricingPlan.product = .failure(IAPError.failedToRequestProduct)
return
}

self.pricingPlan.product = .success(product)
}
}

func request(_ request: SKRequest, didFailWithError error: Error) {
DispatchQueue.main.async {
switch request {
case is SKProductsRequest:
self.pricingPlan.product = .failure(IAPError.failedToRequestProduct)
case is SKReceiptRefreshRequest:
self.pricingPlan.receipt = .failure(IAPError.failedToRefreshReceipt)
default:
break
}

}
}

func requestDidFinish(_ request: SKRequest) {
switch request {
case is SKReceiptRefreshRequest:
checkReceipt()
default:
break
}
}
}

Updated at 2020-12-04 06:58:17

How to disable NSTextView in SwiftUI

Issue #702

The trick is to use an overlay

1
2
3
4
5
6
7
8
9
10
MessageTextView(text: $input.message)
.overlay(obscure)

var obscure: AnyView {
if store.pricingPlan.isPro {
return EmptyView().erase()
} else {
return Color.black.opacity(0.01).erase()
}
}

How to add under highlight to text in css

Issue #701

Use mark. This does not work for multiline

1
2
3
4
5
6
7
8
<p>
<mark css={css`
display: inline-block;
line-height: 0em;
padding-bottom: 0.5em;
`}>{feature.title}
</mark>
</p>

Another way is to use background

1
2
3
.highlight {
background: linear-gradient(180deg,rgba(255,255,255,0) 50%, #FFD0AE 50%);
}

Read more


Updated at 2020-11-20 05:23:59

How to use default system fonts in React apps

Issue #700

In index.css

1
2
3
4
5
6
7
8
9
10
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}

Updated at 2020-11-18 06:29:29

How to make simple overlay container in React

Issue #699

Use term ZStack like in SwiftUI, we declare container as relative position. For now it uses only 2 items from props.children but can be tweaked to support mutiple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class App extends React.Component {
render() {
return (
<ZStack>
<Header />
<div css={css`
padding-top: 50px;
`}>
<Showcase factory={factory} />
<Footer />
</div>
</ZStack>
)
}
}
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
/** @jsx jsx */
import React from 'react';
import { css, jsx } from '@emotion/core'

export default function ZStack(props) {
return (
<div css={css`
position: relative;
border: 1px solid red;
`}>
<div css={css`
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: -1;
`}>
{props.children[0]}
</div>
<div >
{props.children[1]}
</div>

</div>
)
}

Updated at 2021-01-15 22:03:49

How to search using regular expression in Xcode

Issue #698

Xcode has powerful search. We can constrain search to be scoped in workspace, project or some folders. We can also constrain case sensitivity.

Another cool thing that people tend to overlook is, besides searching based on text, we can search based on references, definitions, call hierarchy, and 🎉 regular expressions.

Screenshot 2020-12-11 at 11 27 48

Searching for regular expression gives us extra power when it comes to limit our search based on some criteria. For example when we are about to refactor some NSLayoutAnchor code and we want to find all NSLayoutConstraint. calls that stops at bottomAnchor

Here ‘s how to search NSLayoutConstraint calls that involves bottomAnchor

1
(NSLayoutConstraint(.*[\r\n])*).+?(?=bottomAnchor)
1
2
3
4
5
6
NSLayoutConstraint.activate([
child.leadingAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.leadingAnchor),
child.trailingAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.trailingAnchor),
child.topAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.topAnchor),
child.bottomAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.bottomAnchor)
])

Another tip when searching for regular expression is that we can use https://regex101.com/ to validate and fine tune our regex. Below are breakdowns of our regex. Note how we use /.+?(?=abc)/ to define “search until”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/
(NSLayoutConstraint(.*[\r\n])*).+?(?=bottomAnchor)
/
gm
1st Capturing Group (NSLayoutConstraint(.*[\r\n])*)
NSLayoutConstraint matches the characters NSLayoutConstraint literally (case sensitive)
2nd Capturing Group (.*[\r\n])*
* Quantifier — Matches between zero and unlimited times, as many times as possible, giving back as needed (greedy)
A repeated capturing group will only capture the last iteration. Put a capturing group around the repeated group to capture all iterations or use a non-capturing group instead if you're not interested in the data
.* matches any character (except for line terminators)
* Quantifier — Matches between zero and unlimited times, as many times as possible, giving back as needed (greedy)
Match a single character present in the list below [\r\n]
\r matches a carriage return (ASCII 13)
\n matches a line-feed (newline) character (ASCII 10)
.+? matches any character (except for line terminators)
+? Quantifier — Matches between one and unlimited times, as few times as possible, expanding as needed (lazy)
Positive Lookahead (?=bottomAnchor)
Assert that the Regex below matches
bottomAnchor matches the characters bottomAnchor literally (case sensitive)
Global pattern flags
g modifier: global. All matches (don't return after first match)
m modifier: multi line. Causes ^ and $ to match the begin/end of each line (not only begin/end of string)

Updated at 2020-12-11 10:35:14

How to write to temporary file in Swift

Issue #697

Use temporaryDirectory from FileManager and String.write

1
2
3
4
5
6
7
8
9
10
11
12
func writeTempFile(books: [Book]) -> URL {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("txt")
let string = books
.map({
"book '\($0.url.path)'"
})
.joined(separator: "\n")
try? string.write(to: url, atomically: true, encoding: .utf8)
return url
}

How to use functions with default arguments in Swift

Issue #696

Which methods do you think are used here

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 Cocoa

struct Robot {
let a: Int
let b: Int
let c: Int

init(a: Int = 1, c: Int = 3) {
self.a = a
self.b = 0
self.c = c
print("Init with a=\(a) and c=\(c)")
}

init(a: Int = 1, b: Int = 2, c: Int = 3) {
self.a = a
self.b = b
self.c = c

print("Init with a\(a), b=\(b) and c=\(c)")
}
}

let r1 = Robot(c: 10)
let r2 = Robot(a: 5, c: 10)
let r3 = Robot(a: 5, b: 7, c: 10)
let r4 = Robot(a: 5)
let r5 = Robot(b: 5)

The log is

1
2
3
4
5
Init with a=1 and c=10
Init with a=5 and c=10
Init with a5, b=7 and c=10
Init with a=5 and c=3
Init with a1, b=5 and c=3