How to show save panel in AppKit

Issue #405

Enable Read/Write for User Selected File under Sandbox to avoid bridge absent error

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
func save() {
let panel = NSSavePanel()
// 3
panel.directoryURL = FileManager.default.homeDirectoryForCurrentUser
// 4
panel.nameFieldStringValue = "abc.gif"

// 5
guard let window = view.window else {
return
}

panel.beginSheetModal(for: window) { (result) in
guard result == .OK, let url = panel.url else {
self.showAlert()
return
}
}
}

func showAlert() {
let alert = NSAlert()
alert.messageText = "Hello world"
alert.informativeText = "Information text"
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
alert.runModal()
}

To save multiple files, use NSOpenPanel

1
2
3
4
5
let panel = NSOpenPanel()
panel.canChooseFiles = false
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
panel.directoryURL = FileManager.default.homeDirectoryForCurrentUser

Read more

How to animate NSView using keyframe

Issue #404

1
2
3
4
5
6
7
8
let animation = CAKeyframeAnimation(keyPath: "position.y")
animation.values = [50, 20, 50]
animation.keyTimes = [0.0, 0.5, 1.0]
animation.duration = 2
animation.repeatCount = Float.greatestFiniteMagnitude
animation.autoreverses = true
myView.wantsLayer = true
myView.layer?.add(animation, forKey: "bounce")

How to quit macOS on last window closed

Issue #403

https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428381-applicationshouldterminateafterl?language=objc

The application sends this message to your delegate when the application’s last window is closed. It sends this message regardless of whether there are still panels open. (A panel in this case is defined as being an instance of NSPanel or one of its subclasses.)

If your implementation returns NO, control returns to the main event loop and the application is not terminated. If you return YES, your delegate’s applicationShouldTerminate: method is subsequently invoked to confirm that the application should be terminated.

1
2
3
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}

How to sign executable for sandbox

Issue #401

Find identity

1
security find-identity

Sign with entitlements and identity. For macOS, use 3rd Party Mac Developer Application

1
codesign -f -s "3rd Party Mac Developer Application: Khoa Pham (123DK123F2)" --entitlements "MyApp.entitlements" "tool/mytool"

To enable harden runtime

1
codesign --verbose --force --deep -o runtime --sign

How to cache URLSession response

Issue #339

For simple cases, we don’t need to. Let’s use urlCache

The URL cache for providing cached responses to requests within the session.

Accessing Cached Data

The URL Loading System caches responses both in memory and on disk, improving performance and reducing network traffic.

The URLCache class is used for caching responses from network resources. Your app can directly access the shared cache instance by using the shared property of URLCache. Or, you can create your own caches for different purposes, setting distinct caches on your URLSessionConfiguration objects.

Read more

How to use NSSecureCoding in Swift

Issue #334

NSSecureCoding has been around since iOS 6 and has had some API changes in iOS 12

A protocol that enables encoding and decoding in a manner that is robust against object substitution attacks.

https://developer.apple.com/documentation/foundation/nscoder/2292924-decodeobject

If the coder responds true to requiresSecureCoding, then the coder calls failWithError(_:) in either of the following cases:
The class indicated by cls doesn’t implement NSSecureCoding.
The unarchived class doesn’t match cls, nor do any of its superclasses.

If the coder doesn’t require secure coding, it ignores the cls parameter and does not check the decoded object.

The class must subclass from NSObject and conform to NSSecureCoding

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
class Note: NSObject, NSSecureCoding {
static var supportsSecureCoding: Bool = true

func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(text, forKey: "text")
aCoder.encode(date, forKey: "date")
}

required init?(coder aDecoder: NSCoder) {
guard
let id = aDecoder.decodeObject(of: [NSString.self], forKey: "id") as? String,
let text = aDecoder.decodeObject(of: [NSString.self], forKey: "text") as? String,
let date = aDecoder.decodeObject(of: [NSDate.self], forKey: "date") as? Date
else {
return nil
}

self.id = id
self.text = text
self.date = date
}

let id: String
var text = "untitled"
var date: Date = Date()

override init() {
id = UUID().uuidString
super.init()
}
}

First, we need to serialize to Data, then use EasyStash for easy persistency

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
do {
let securedItems = items.map({ SecuredClientLoggerItem(item: $0) })
if #available(iOS 11.0, *) {
let data = try NSKeyedArchiver.archivedData(
withRootObject: securedItems,
requiringSecureCoding: true
)

try data.write(to: fileUrl)
} else {
_ = NSKeyedArchiver.archiveRootObject(
securedItems,
toFile: fileUrl.path
)
}
} catch {
print(error)
}

Then we can use unarchiveTopLevelObjectWithData to unarchive array

1
2
3
4
5
6
7
do {
let data = try Data(contentsOf: fileUrl)
let notes = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Note]
// notes is of type [Note]?
} catch {
print(error)
}

Note that for UUID, NSCoding seems to convert to UUID instead of String

1
2
3
4
let id = aDecoder.decodeObject(
of: [NSUUID.self],
forKey: "id"
) as? UUID,

How to use moveItem in NSCollectionView in AppKit

Issue #332

From moveItem(at:to:)

Moves an item from one location to another in the collection view.

After rearranging items in your data source object, use this method to synchronize those changes with the collection view. Calling this method lets the collection view know that it must update its internal data structures and possibly update its visual appearance. You can move the item to a different section or to a new location in the same section. The collection view updates the layout as needed to account for the move, animating cells into position in response.

When inserting or deleting multiple sections and items, you can animate all of your changes at once using the performBatchUpdates(_:completionHandler:) method.

1
2
3
4
5
6
notes.swapAt(index, 0)

collectionView.animator().moveItem(
at: index.toIndexPath(),
to: 0.toIndexPath()
)

There may be unknown reasons or bug that make other cells stay in incorrect state. The fix is to reload the rest cells

1
2
let set = Set((1..<notes.count).map({ $0.toIndexPath() }))
collectionView.reloadItems(at: set)

How to show dropdown from NSSegmentedControl in AppKit

Issue #331

From NSSegmentedControl

The features of a segmented control include the following:
A segment can have an image, text (label), menu, tooltip, and tag.
A segmented control can contain images or text, but not both.

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
let languageMenu = NSMenu(title: "")
let languages = ["Swift", "Javascript"]
languages.forEach {
let item = NSMenuItem(title: $0, action: #selector(handleSelectLanguage(_:)), keyEquivalent: "")
item.target = self
item.isEnabled = true
languageMenu.addItem(item)
}

let themeMenu = NSMenu(title: "")
let themes = ["one dark", "one light"]
themes.forEach {
let item = NSMenuItem(title: $0, action: #selector(handleSelectLanguage(_:)), keyEquivalent: "")
item.target = self
item.isEnabled = true
themeMenu.addItem(item)
}

segment.segmentCount = 2
segment.selectedSegmentBezelColor = NSColor.red

segment.setLabel("Language", forSegment: 0)
segment.setLabel("Theme", forSegment: 1

segment.setMenu(languageMenu, forSegment: 0)
segment.setMenu(themeMenu, forSegment: 1

segment.showsMenuIndicator(forSegment: 0)
segment.showsMenuIndicator(forSegment: 1)

How to make scrollable NSTextView in AppKit

Issue #330

When adding NSTextView in xib, we see it is embedded under NSClipView. But if we try to use NSClipView to replicate what’s in the xib, it does not scroll.

To make it work, we can follow Putting an NSTextView Object in an NSScrollView and How to make scrollable vertical NSStackView to make our ScrollableInput

For easy Auto Layout, we use Anchors for UIScrollView.

Things worth mentioned for vertical scrolling

1
2
3
textContainer.heightTracksTextView = false
textView.autoresizingMask = [.width]
textView.isVerticallyResizable = true
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
class ScrollableInput: NSView {
let scrollView = NSScrollView()
let textView = NSTextView()

override init(frame frameRect: NSRect) {
super.init(frame: frameRect)

let rect = CGRect(
x: 0, y: 0,
width: 0, height: CGFloat.greatestFiniteMagnitude
)

let layoutManager = NSLayoutManager()

let textContainer = NSTextContainer(size: rect.size)
layoutManager.addTextContainer(textContainer)
textView = NSTextView(frame: rect, textContainer: textContainer)
textView.maxSize = NSSize(width: 0, height: CGFloat.greatestFiniteMagnitude)

textContainer.heightTracksTextView = false
textContainer.widthTracksTextView = true

textView.isRichText = false
textView.importsGraphics = false
textView.isEditable = true
textView.isSelectable = true
textView.font = R.font.text
textView.textColor = R.color.text
textView.isVerticallyResizable = true
textView.isHorizontallyResizable = false

addSubview(scrollView)
scrollView.hasVerticalScroller = true
scrollView.drawsBackground = false
scrollView.drawsBackground = false
textView.drawsBackground = false

activate(
scrollView.anchor.edges
)

scrollView.documentView = textView
textView.autoresizingMask = [.width]
}

required init?(coder decoder: NSCoder) {
fatalError()
}
}

From macOS 10.14, we can use NSTextView.scrollableTextView()

Updated at 2020-12-31 05:43:41

How to make simple form validator in Swift

Issue #328

Sometimes we want to validate forms with many fields, for example name, phone, email, and with different rules. If validation fails, we show error message.

We can make simple Validator and Rule

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
class Validator {
func validate(text: String, with rules: [Rule]) -> String? {
return rules.compactMap({ $0.check(text) }).first
}

func validate(input: InputView, with rules: [Rule]) {
guard let message = validate(text: input.textField.text ?? "", with: rules) else {
input.messageLabel.isHidden = true
return
}

input.messageLabel.isHidden = false
input.messageLabel.text = message
}
}

struct Rule {
// Return nil if matches, error message otherwise
let check: (String) -> String?

static let notEmpty = Rule(check: {
return $0.isEmpty ? "Must not be empty" : nil
})

static let validEmail = Rule(check: {
let regex = #"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,64}"#

let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
return predicate.evaluate(with: $0) ? nil : "Must have valid email"
})

static let countryCode = Rule(check: {
let regex = #"^\+\d+.*"#

let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
return predicate.evaluate(with: $0) ? nil : "Must have prefix country code"
})
}

Then we can use very expressively

1
2
let validator = Validator()
validator.validate(input: inputView, with: [.notEmpty, .validEmail])

Then a few tests to make sure it works

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ValidatorTests: XCTestCase {
let validator = Validator()

func testEmpty() {
XCTAssertNil(validator.validate(text: "a", with: [.notEmpty]))
XCTAssertNotNil(validator.validate(text: "", with: [.notEmpty]))
}

func testEmail() {
XCTAssertNil(validator.validate(text: "onmyway133@gmail.com", with: [.validEmail]))
XCTAssertNotNil(validator.validate(text: "onmyway133", with: [.validEmail]))
XCTAssertNotNil(validator.validate(text: "onmyway133.com", with: [.validEmail]))
}

func testCountryCode() {
XCTAssertNil(validator.validate(text: "+47 11 222 333", with: [.countryCode]))
XCTAssertNotNil(validator.validate(text: "11 222 333", with: [.countryCode]))
XCTAssertNotNil(validator.validate(text: "47 11 222 333", with: [.countryCode]))
}
}

allSatisfy

To check if all rules are ok, we can use reduce

1
2
3
func check(text: String, with rules: [Rule]) -> Bool {
return rules.allSatisfy({ $0.check(text).isOk })
}

Or more concisely, use allSatisfy

1
2
3
4

func check(text: String, with rules: [Rule]) -> Bool {
return rules.allSatisfy({ $0.check(text).isOk })
}

How to animate NSCollectionView changes

Issue #323

Use proxy animator()

1
2
3
4
5
let indexPath = IndexPath(item: index, section: 0)
collectionView.animator().deleteItems(at: Set(arrayLiteral: indexPath))

let indexPath = IndexPath(item: 0, section: 0)
collectionView.animator().insertItems(at: Set(arrayLiteral: indexPath))

How to handle right click in AppKit

Issue #322

1
2
3
4
5
lazy var gr = NSClickGestureRecognizer(target: self, action: #selector(onPress(_:)))

gr.buttonMask = 0x2
gr.numberOfClicksRequired = 1
view.addGestureRecognizer(gr)

How to show context menu in NSCollectionView

Issue #321

Detect locationInWindow in NSEvent

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
class ClickedCollectionView: NSCollectionView {
var clickedIndex: Int?

override func menu(for event: NSEvent) -> NSMenu? {
clickedIndex = nil

let point = convert(event.locationInWindow, from: nil)
for index in 0..<numberOfItems(inSection: 0) {
let frame = frameForItem(at: index)
if NSMouseInRect(point, frame, isFlipped) {
clickedIndex = index
break
}
}

return super.menu(for: event)
}
}

let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Delete", action: #selector(didSelectDelete(_:)), keyEquivalent: ""))
collectionView.menu = menu

@objc func didSelectDelete(_ item: NSMenuItem) {
guard
let index = collectionView.clickedIndex,
index < notes.count
else {
return
}

let indexPath = IndexPath(item: index, section: 0)
notes.remove(at: index)
collectionView.deleteItems(at: Set(arrayLiteral: indexPath))
}

For NSCollectionView with more than 1 sections

1
let frame = layoutAttributesForItem(at: IndexPath(item: index, section: 0))?.frame ?? .zero

Use Omnia

Omnia supports clicked indexPath for multi section NSCollectionView

1
2
3
collectionViewHandler.addMenuItem(title: "Add to Favorite", action: { item in
print(item)
})

How to customize NSTextView in AppKit

Issue #320

Scrollable

textview

Embed image or NSTextAttachmentCellProtocol

  • Select TextView
  • Select Rich Text and Graphics
  • Select Size Inspector -> Resizable and tick both Horizontally and Vertically

Customize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scrollView.drawsBackground = false
textView.drawsBackground = false
textView.string = "What's on your mind?"
textView.delegate = self
textView.selectedTextAttributes = [
NSAttributedString.Key.backgroundColor: NSColor(hex: "414858"),
NSAttributedString.Key.foregroundColor: NSColor(hex: "ACB2BE")
]

extension MainView: NSTextViewDelegate {
func textViewDidChangeSelection(_ notification: Notification) {
// Change text color again after image dragging
}
}

How to use custom font in AppKit

Issue #319

  • Add fonts to targets
  • Declare in Info.plist

https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/GeneralPurposeKeys.html#//apple_ref/doc/uid/TP40009253-SW8

ATSApplicationFontsPath (String - macOS) identifies the location of a font file or directory of fonts in the bundle’s Resources directory. If present, macOS activates the fonts at the specified path for use by the bundled app. The fonts are activated only for the bundled app and not for the system as a whole. The path itself should be specified as a relative directory of the bundle’s Resources directory. For example, if a directory of fonts was at the path /Applications/MyApp.app/Contents/Resources/Stuff/MyFonts/, you should specify the string Stuff/MyFonts/ for the value of this key.

1
2
<key>ATSApplicationFontsPath</key>
<string>.</string>
  • Reference by name
1
NSFont(name: "FiraCode-Bold", size: 14)

How to avoid crash when closing NSWindow for agent macOS app

Issue #312

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ClosableWindow: NSWindow {
override func close() {
self.orderOut(NSApp)
}
}

let window = ClosableWindow(
contentRect: rect,
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
}

window.makeKeyAndOrderFront(NSApp)

The reason is that window is released upon closed if it is not owned by NSWindowController, or we can use releasedWhenClosed

The value of this property is YES if the window is automatically released after being closed; NO if it’s simply removed from the screen.

The default for NSWindow is YES; the default for NSPanel is NO. Release when closed, however, is ignored for windows owned by window controllers. Another strategy for releasing an NSWindow object is to have its delegate autorelease it on receiving a windowShouldClose: message.


Updated at 2020-12-14 05:18:30

How to style NSButton in AppKit

Issue #297

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let button = NSButton()
button.wantsLayer = true
button.isBordered = false
button.setButtonType(.momentaryChange)
button.attributedTitle = NSAttributedString(
string: "Click me",
attributes: [
NSAttributedString.Key.foregroundColor: NSColor.white,
NSAttributedString.Key.font: NSFont.labelFont(ofSize: 13)
]
button.layer?.backgroundColor = NSColor.orange.cgColor
button.layer?.cornerRadius = 12

activate(
button.anchor.height.equal.to(32),
button.anchor.width.equal.to(100)
)

To make it have native rounded rect

1
2
3
4
button.imageScaling = .scaleProportionallyDown
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
button.isBordered = true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import AppKit
import Omnia

extension NSButton {
func style(imageName: String) {
image = NSImage(named: NSImage.Name(imageName))
isBordered = false
imageScaling = .scaleProportionallyDown
}

func styleAction(title: String) {
attributedTitle = NSAttributedString(string: title, attributes: [
NSAttributedString.Key.foregroundColor: NSColor(hex: "008B80"),
NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 13)
])

isBordered = false
setButtonType(.momentaryChange)
}
}

Read more

20 recommended utility apps for macOS

Issue #274

Original post https://hackernoon.com/20-recommended-utility-apps-for-macos-in-2018-ea494b4db72b


Depending on the need, we have different apps on the mac. As someone who worked mostly with development, below are my indispensable apps. They are like suits to Tony Stark. Since I love open source apps, they have higher priority in the list.

Open source apps

iTerm 2 https://www.iterm2.com/

iTerm2 is a replacement for Terminal and the successor to iTerm. It works on Macs with macOS 10.10 or newer. iTerm2 brings the terminal into the modern age with features you never knew you always wanted.

iTerm2 has good integration with tmux and supports Split Panes

Term2 allows you to divide a tab into many rectangular “panes”, each of which is a different terminal session. The shortcuts cmd-d and cmd-shift-d divide an existing session vertically or horizontally, respectively. You can navigate among split panes with cmd-opt-arrow or cmd-[ and cmd-]. You can “maximize” the current pane — hiding all others in that tab — with cmd-shift-enter. Pressing the shortcut again restores the hidden panes.

There’s the_silver_searcher with ag command to quickly search for files

oh-my-zsh https://github.com/robbyrussell/oh-my-zsh

A delightful community-driven (with 1,200+ contributors) framework for managing your zsh configuration. Includes 200+ optional plugins (rails, git, OSX, hub, capistrano, brew, ant, php, python, etc), over 140 themes to spice up your morning, and an auto-update tool so that makes it easy to keep up with the latest updates from the community.

I use z shell with oh-my-zsh plugins. I also use zsh-autocompletions to have autocompletion like fish shell and z to track and quickly navigate to the most used directories.

spectacle https://github.com/eczarny/spectacle

Spectacle allows you to organize your windows without using a mouse.

With spectable, I can organise windows easily with just Cmd+Option+F or Cmd+Option+Left

insomnia https://github.com/getinsomnia/insomnia

Insomnia is a cross-platform REST client, built on top of Electron.

Regardless if you like electron.js apps or not. This is a great tool for testing REST requets

Visual Studio Code https://github.com/Microsoft/vscode

VS Code is a new type of tool that combines the simplicity of a code editor with what developers need for their core edit-build-debug cycle. Code provides comprehensive editing and debugging support, an extensibility model, and lightweight integration with existing tools.

This seems to be the most popular for front end development, and many other things. There’s bunch of extensions that make the experience to a new level.

IconGenerator https://github.com/onmyway133/IconGenerator

Built by me. When developing iOS, Android and macOS applications, I need a quick way to generate icons in different sizes. You can simply drag the generated asset into Xcode and that’s it.

vmd https://github.com/yoshuawuyts/vmd

Preview markdown files in a separate window. Markdown is formatted exactly the same as on GitHub.

colorpicker https://github.com/Toinane/colorpicker

A mininal but complete colorpicker desktop app

I used to use Sip but I often get the problem of losing focus.

GifCapture https://github.com/onmyway133/GifCapture

I built this as a native macOS app to capture screen and save to gif file. It works like Licecap but open source. There’s also an open source tool called kap that is slick.

Itsycal https://github.com/sfsam/Itsycal

Itsycal is a tiny calendar for your Mac’s menu bar.

The app is minimal and works very well. It can shows calendar for integrated accounts in the mac.

PushNotifications https://github.com/onmyway133/PushNotifications

I often need to test push notification to iOS and Android apps. And I want to support both certificate and key p8 authentication for Apple Push Notification service, so I built this tool.

Lyrics https://github.com/onmyway133/Lyrics

A menu bar app to show the lyric of the playing Spotify song

When I listen to some songs in Spotify, I want to see the lyrics too. The lyrics is fetched from https://genius.com/ and displayed in a wonderful UI.

gitify https://github.com/manosim/gitify

GitHub Notifications on your desktop.

I use this to get real time notification for issues and pull requests for projects on GitHub. I hope there is support for Bitbucket soon.

FinderGo https://github.com/onmyway133/FinderGo

FinderGo is both a native macOS app and a Finder extension. It has toolbar button that opens terminal right within Finder in the current directory. You can configure it to open either Terminal, iTerm2 or Hyper

Atom one dark theme

This is about theme. There is the very popular dracular themes, but I find it too strong for the eyes. I don’t use Atom, but I love its one dark UI. I used to maintain my own theme for Xcode called DarkSide but now I use xcode-one-dark for Xcode and Atom One Dark Theme for Visual Studio Code.

I also use Fira Code font in Xcode, Visual Studio Code and Android Studio, which has beautiful ligatures.

Chrome extensions

I use Chrome for its speed and wonderful support for extensions. The extensions I made are github-chat to enable chat within GitHub and github-extended to see more pinned repositories.

There are also refined github, github-repo-size and octotree that are indispensable for me.

caprine https://github.com/sindresorhus/caprine

Caprine is an unofficial and privacy focused Facebook Messenger app with many useful features.

Close source and commercial apps

Sublime Text https://www.sublimetext.com/

Sublime Text is a sophisticated text editor for code, markup and prose. You’ll love the slick user interface, extraordinary features and amazing performance.

Sublime Text is simply fast and the editing experience is very good. I’ve used Atom but it is too slow.

Sublime Merge https://www.sublimemerge.com/

Meet a new Git client, from the makers of Sublime Text

Sublime Merge never lets me down. The source control app is simply very quick. I used SourceTree in the past, but it is very slow and had problem with authentication to Bitbucket and GitHub, and it halts very often for React Native apps, which has lots of node modules committed.

1 Password https://1password.com/

1Password remembers them all for you. Save your passwords and log in to sites with a single click. It’s that simple.

Everyone need strong and unique passwords these day. This tool is indispensable

Monosnap https://monosnap.com/welcome

Make screenshots. Draw on it. Shoot video and share your files. It’s fast, easy and free.

I haven’t found a good open source alternative, this is good in capturing screen or portion of the screen.

VLC https://www.videolan.org/index.nb.html

iTunes or Quick Time has problem with some video codecs. This app VLC can play all kinds of video types.

Xcode https://developer.apple.com/xcode/

Xcode is the go to editor for iOS developer. The current version is Xcode 10. From Xcode 8, plugins are not supported. The way to go is Xcode extensions.

I have developed XcodeColorSense2 to easily recognise hex colors, and XcodeWay to easily navigate to many places right from Xcode

Sketch https://www.sketchapp.com/

Sketch is a design toolkit built to help you create your best work — from your earliest ideas, through to final artwork.

Sketch is the most favorite design tool these days. There are many cool plugins for it. I use Sketch-Action and User Flows

Where to go from here

I hope you find some new tools to try. If you know other awesome tools, feel free to make a comment. Here are some more links to discover further

Favorite WWDC 2018 sessions

Issue #245

Original post https://medium.com/fantageek/my-favourite-wwdc-2018-sessions-363d3fc9c9d5


Favourite WWDC 2018 sessions

This year I failed the lottery ticket to WWDC, and I also missed the keynote live stream because I was sailing on the Christian Radich outside Oslo fjord that day. Luckily all the videos are available on Apple Developer site very shortly, and we can watch them now on Chrome or the unofficial WWDC app on macOS. I recommend the WWDC macOS app as it allows to mark favourites and filter, also offers the ability to adjust play speed to 1.25 or 1.5 saves me some time watching.

This year WWDC focuses a lot on privacy, stability, and speed, which are all I wish, so many thanks to Apple engineers who made that happen, and the resit to install the so called more stable iOS 12 is real. As an iOS engineers, I like to focus more about the necessary things to me, that is about the Swift programming language, new changes in Cocoa Touch, enhancements in Xcode and testing tricks. I also like to explore more about machine learning so I’m very glad that Apple is investing more into this technology with the introduction of Turi Create and Create ML.

To me, APIs come and get deprecated very often and it’s good to know them, but the most important thing is to invest in your programming, debugging and testing skill which you can apply in many other platforms.

Continued from last year favourites list, below are my favourite sessions with personal notes. Things are listed in no particular order. Hope you find it useful.

Platforms State of the Union

If you don’t have time, you should watch only this session. Platform State of the Union is like keynote for developers as it highlights important changes.

  • Privacy: Apple confirms on its commitment in privacy and security, also introduces password management feature and auto fill on iOS 12. Generating strong password, integrating with 3rd password management and quickly populating OTP field from SMS message have never been easier. GateKeeper gets some improvements as well and begins to require app to be notarised.

  • iOS 12: huge improvement in performance, Siri gets smarter with Shortcut support, group calling in FaceTime and grouped notification. Also for emoji fan, Memoji was introduced.

  • macOS 10.14 Mojave: more with Dark Mode. They demo mostly with Xcode in dark mode, which looks so cool. This year WWDC banner give hints about iOS and macOS cross-platform apps, which is partially true with Marzipan, a way to allow iOS apps to run on the mac.

  • Xcode 10: with improvements in code editing and source control changes bar indicator. Debugging with memory debug tool, LLDB performance enhancement and particular the new build system completely rewritten in Swift with parallel tasks are exciting news.

  • Swift 4.2: if you follow swift repo then Swift 4.2 may not be very surprising. There are also announcements for Swift 5 plan.

  • Machine Learning: is never hotter than this. This year we see huge investments in machine learning with Create ML, Turi Create, Natural Language frameworks, CoreML 2, new detection capabilities in Vision.

  • ARKit 2, watchOS 5, tvOS 12, AppStore Connect and AppStore Connect APIs are some other important news you don’t want to miss.

What’s new in Swift

Together with this session, I recommend you to read What’s new in Swift 4.2 summary which is very succinct. Besides improvement in complication and runtime, Swift 4.2 offers some new features: iterable enum case, synthesised Equatable and Hashable, handy functions for shuffling, random generating. To me, the need to explicitly handle Implicitly unwrapped optional is also a reasonable change.

What’s New in Cocoa Touch

This is a brief introduction to all changes coming to iOS 12, together with tips on how to be a good iOS citizen. Learn what can affect scrolling experience and prefetching technique, memory consumption and automatic backing stores, how to get the best from UIImage and UIImageView . AutoLayout engine got a lot of performance improvement so it won’t bother you any more. To me the most satisfying is to get all the UIKit notifications and classes reorganised under nested types, which makes code reasoning very easy.

Getting the Most out of Playgrounds in Xcode

I’ve written about Playground before and I’m very glad that Apple also invests a lot in it. The way people can interact and train model [Create ML](http://Introducing Create ML) in Playground is mesmerising. People may question how Playground works so well in session’s demos, but we can’t resist the new changes coming to Playground like Step by Step execution, markup rendering improvements and also how easy it is to consume custom frameworks. We can also now publish our own Playground through subscription.

What’s New in Core ML

Apple starts the machine learning trend last year with the introduction of Core ML. We might be excited and frustrated at the same time as Core ML is powerful but there’s no way we can customise it. Now the 2 parts tell us how to implement custom layer and model, techniques to reduce model size like quantisation and flexible model. This makes the foundation for improvement in Vision in object tracking and the debut of Natural Language framework. Machine learning has never been easier.

What’s New in Testing

I can’t miss any testing sessions as it is part of every day’s work. How can your program avoids regression bugs and ready for refactoring without any tests?

This session shows improvement in coverage and the introduction of xccov tool to help us build automation on top of coverage report. Parallel distributed testing in Xcode 10 can save us some time to have coffee. Another wonderful news is that tests have multiple order execution mode to avoid bugs due to implicit dependencies.

Testing Tips & Tricks 🌟

This is my most favourite. The session starts with a pyramid of tests with unit integration and end-to-end test analogy explanation, then to some very cool tips and tricks.

  • Testing network request: I like the separation of APIRequest and APIRequestLoader with URLSession , dependency injection with default parameter and the customisation of URLProtocol in URLSessionConfiguration

  • Testing notification: Notification is system wide and I try to avoid it as much as possible. This shows how to inject dependency with default parameter and use of own NotificationCenter instead of NotificationCenter.default to ease testing

  • Testing location: build abstraction with LocationProvider and LocationFetcher . How to use custom protocol and protocol for delegate to mock during test

  • Testing timer: how to use and mock RunLoop behaviour with Timer

Advanced Debugging with Xcode and LLDB

LLDB has been improved to enable to debugging reliability experience, issues with AST context corruption, Swift type resolution are now things in the past. We can review how to use some common LLDB commands with handy arguments, and how to use Xcode breakpoint to its best.

A Tour of UICollectionView 🌟

I begin to use UICollectionView more than UITableView , and it also has same code as NSCollectionView,which is more comfortable solution than the horrible NSTableView .

  • Item size in UICollectionViewLayout : I often rely on UICollectionViewDelegateFlowLayout to specify item size but after watching this session, I feel like moving size related code to Layout object feels more like a good way to go

  • Mosaic layout: This is not new, but good to watch again. You learn how to implement custom layout using cached layout attributes

  • Data Source update: I didn’t expect Apple mentions this, but it is a good lesson on how UICollectionView handles batch update. I ‘ve written about this before in my A better way to update UICollectionView data in Swift with diff framework and that post gets a lot of attractions. In this session we need to remember that *ordering matters in data source update, but not in collection view update *❗️❗️❗️

Swift Generics

Generic was a core feature of Swift since the beginning, we all know that it helps us to implement generic code that can work with many types in a type safe manner. This session reminds that I ‘ve never learned enough, especially the reasonable design behind it.

The sessions showcases Collection protocol and its protocol inheritances: MutableCollection , BidirectionalCollection , RandomAccessCollection and how they can be combined to provide generic functionalities for conformers. The associatedtype requirement in each protocol, especially Index and Element, is very minimal and has enough constraints for the protocol to implement lots of necessary functionalities in its protocol extension, which is eye opening for me. I very like to read open source, so looking at the source code for such protocols helps me understand more.

The part about Fisher Yates shuffle algorithm details how we can come up with necessary protocol while still make them generic

Pay attention to when they mention count and map , you can learn more how each concrete type can hook into the customisation point in protocol extension

Finally learn the Liskov substitution principle with protocol in class inheritance. You should also Using Collections Effectively for how to utilise handy functions in Collection.

Data You Can Trust

Although Codable has a lot to offers in term of data integrity, this is good to know about to make sure the data you receive is actually the right data in correct format and structure. CommonCrypto is also part of new iOS SDK so you don’t need my Arcane library to handle encryption and hashing in your apps.

Embracing Algorithms

This is the most pleasant to watch as it is like a conversation between the speaker and the imaginary manager Crusty. Here I learn how to be aware of algorithm complexity and also how to utilise built in Foundation functions which are already optimised for performance.

After this session I can’t help myself but going to Swift repo to read the Algorithms.swift file immediately.

Image and Graphics Best Practices

Learn how image encoding and decoding works through data and image buffer and how that affects memory and performance. There are techniques like downsampling that can tackle this problem. This also recommends against using backing store, and instead, use UIImageView

A Guide to Turi Create

I’ve written about Turi Create before, but it is just scratching the surface of the many tasks offered by Turi. This year Apple releases Turi Create 5 with style transfer task, Vision Feature Print, GPU acceleration and recommender model improvements. I can’t wait to explore. And if you take a look at MLDataTable in Create ML framework, it looks like this has Turi ‘s SFrame under the hood.

That’s it. Thanks for reading. What are your favourite sessions this year? Please share in the comment section below

How to get running window informations in macOS

Issue #243

From https://developer.apple.com/documentation/coregraphics/1455137-cgwindowlistcopywindowinfo

Generates and returns information about the selected windows in the current user session.

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 MyWindowInfo {
let frame: CGRect
let name: String
let pid: Int
let number: Int

init?(json: [String: Any]) {
guard let pid = json["kCGWindowOwnerPID"] as? Int else {
return nil
}

guard let name = json["kCGWindowOwnerName"] as? String else {
return nil
}

guard let rect = json["kCGWindowBounds"] as? [String: Any] else {
return nil
}

guard let x = rect["X"] as? CGFloat else {
return nil
}

guard let y = rect["Y"] as? CGFloat else {
return nil
}

guard let height = rect["Height"] as? CGFloat else {
return nil
}

guard let width = rect["Width"] as? CGFloat else {
return nil
}

guard let number = json["kCGWindowNumber"] as? Int else {
return nil
}

self.pid = pid
self.name = name
self.number = number
self.frame = CGRect(x: x, y: y, width: width, height: height)
}
}

guard let jsons = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID) as? [[String: Any]] else {
return
}

let infos = jsons.compactMap({ MyWindowInfo(json: $0) })

How to show full screen window programmatically in macOS

Issue #242

1
2
3
4
5
let window = NSWindow(contentRect: mainScreen.frame, styleMask: .borderless, backing: .buffered, defer: false)
window.level = .floating
window.contentView = NSView()
window.makeKeyAndOrderFront(NSApp)
NSApp.activate(ignoringOtherApps: true)

and then later hide it

1
window.orderOut(NSApp)

How to work around app damaged warning in macOS

Issue #238

“App” is damaged and can’t be opened. You should move it to the Trash.

👉 Disable gate keeper

1
2
sudo spctl --master-disable
spctl --status

Current workaround is to remove Launch At Login handling code.

How to shake NSView in macOS

Issue #233

Animation on macOS using CAAnimation

Shake

1
2
3
4
5
6
7
8
9
10
let midX = box.layer?.position.x ?? 0
let midY = box.layer?.position.y ?? 0

let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.06
animation.repeatCount = 4
animation.autoreverses = true
animation.fromValue = CGPoint(x: midX - 10, y: midY)
animation.toValue = CGPoint(x: midX + 10, y: midY)
box.layer?.add(animation, forKey: "position")

Animation on macOS using NSAnimationContext

Wiggle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSAnimationContext.runAnimationGroup({ context in
let animation = CAKeyframeAnimation(keyPath: "transform")
animation.beginTime = CACurrentMediaTime() + 5.0
animation.duration = 0.1
animation.autoreverses = true
let wobbleAngle: CGFloat = 0.08
animation.values = [
NSValue(caTransform3D: CATransform3DMakeRotation(wobbleAngle, 0.0, 0.0, 1.0)),
NSValue(caTransform3D: CATransform3DMakeRotation(-wobbleAngle, 0.0, 0.0, 1.0))
]
view.layer?.add(animation, forKey: "transform")
}, completionHandler: {
self.makeAnimation(view: view)
})

Animation on iOS using UIView animation block

1
2
3
4
5
6
7
8
extension UIView {
func shake() {
self.transform = CGAffineTransform(translationX: 16, y: 0)
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
self.transform = CGAffineTransform.identity
}, completion: nil)
}
}

How to run AppleScript in macOS

Issue #223

Surround script by single quotes

1
2
3
4
5
6
7
8
9
10
11
12
let script = 
"""
tell application "XcodeWay"
activate
end tell
"""

let command = "osascript -e '\(script)'"

let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["-c", command]

Run as administrator

In terminal, we can

1
2
cd ~/Library
sudo mkdir MyNewFolder

In code, we use with administrator privileges, this when run will ask for password or fingerprint

1
do shell script "mkdir MyNewFolder" with administrator privileges

How to fix not found zlib problem in macOS Mojave

Issue #217

https://developer.apple.com/documentation/xcode_release_notes/xcode_10_release_notes

The command line tools will search the SDK for system headers by default. However, some software may fail to build correctly against the SDK and require macOS headers to be installed in the base system under /usr/include. If you are the maintainer of such software, we encourage you to update your project to work with the SDK or file a bug report for issues that are preventing you from doing so. As a workaround, an extra package is provided which will install the headers to the base system. In a future release, this package will no longer be provided. You can find this package at:

/Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg


To make sure that you’re using the intended version of the command line tools, run xcode-select -s or xcode select -s /Library/Developer/CommandLineTools after installing.

Run

1
open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg

How to update NSMenuItem while NSMenu is showing in macOS

Issue #213

Use Runloop

1
2
3
4
5
6
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] _ in
let date = Date()
self?.updateStopItem(seconds: finishDate.timeIntervalSince1970 - date.timeIntervalSince1970)
})

RunLoop.main.add(timer!, forMode: .common)

Use Dispatch

1
2
3
4
5
6
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] _ in
let date = Date()
DispatchQueue.main.async {
self?.updateStopItem(seconds: finishDate.timeIntervalSince1970 - date.timeIntervalSince1970)
}
})

Updated at 2020-09-08 07:54:36

How to launch app at start up in macOS

Issue #205

ServiceManagement framework

https://developer.apple.com/documentation/servicemanagement/1501557-smloginitemsetenabled

SMLoginItemSetEnabled

Enable a helper application located in the main application bundle’s “Contents/Library/LoginItems” directory.

Login items

macOS Sierra: Open items automatically when you log in

You can have apps, documents, folders, or server connections open automatically whenever you log in to your Mac.

Add or remove automatic items
Choose Apple menu > System Preferences, then click Users & Groups.

How to notarize macOS app

Issue #203

New Notarization Requirements

https://developer.apple.com/news/?id=04102019a

With the public release of macOS 10.14.5, we require that all developers creating a Developer ID certificate for the first time notarize their apps, and that all new and updated kernel extensions be notarized as well

Signing Your Apps for Gatekeeper

https://developer.apple.com/developer-id/

Unpublished Software. It’s easy to get unpublished software notarized with the Export process or xcodebuild. Custom build workflows are supported by the xcrun altool command line tool for uploading, and you can use xcrun stapler to attach the ticket to the package.

Published Software. To submit software you’ve already published, upload it using the xcrun altool command line tool. Several file types are supported, including .zip, .pkg, and .dmg, so you can upload the same package you already distribute to users.

Notarizing Your App Before Distribution

https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution

When you click Next, Xcode uploads your archive to the notary service. When the upload is complete, the notary service begins the scanning process, which usually takes less than an hour. While the notary service scans your software, you can continue to prepare your archive for distribution. For example, you can export the archive and perform any final testing that you require prior to making your software available to customers.

When the notarization process finishes, Xcode downloads the ticket and staples it to your archive. At that point, export your archive again to receive a distributable version of your software that includes the notary ticket.

Upload a macOS app to be notarized

https://help.apple.com/xcode/mac/current/#/dev88332a81e

First, upload your macOS app to Apple to be notarized. If the upload fails, view the upload logs to find the problem. For example, you must enable hardened runtime (macOS) before you upload the app. Otherwise, check the notarization status and when the status is “Ready for distribution”, export the app for distribution.

Distribute outside the Mac App Store (macOS)

https://help.apple.com/xcode/mac/current/#/dev033e997ca

In some cases, you may want to distribute an app outside of the Mac App Store. Because the app won’t be distributed by Apple, assure users that you are a trusted developer by signing your app with a Developer ID certificate. Users gain additional assurance if your Developer ID-signed app is also notarized by Apple.

On macOS, if your app isn’t downloaded from the Mac App Store or signed with a Developer ID certificate, it won’t launch unless the user completely disables Gatekeeper. Users have the option of enabling or disabling identified developers in System Preferences.

How to use shared AppGroup UserDefaults in macOS and Xcode extension

Issue #201

  • Go to both app and extension target, under Capabilities, enable AppGroup

  • Specify $(TeamIdentifierPrefix)group.com.onmyway133.MyApp

  • $(TeamIdentifierPrefix) will expand to something like T78DK947F3., with .

  • Then using is like a normal UserDefaults

1
2
3
4
let defaults = UserDefaults(suiteName: "T78DK947F3 .group.com.onmyway133.MyApp")

defaults?.set(true, forKey: "showOptions")
defaults?.synchronize()

Updated at 2020-11-12 19:57:33