Dan Rosenstark, author of MIDI Designer, on Tech & Music

Dan Rosenstark

Dan Rosenstark, Author & CEO of MIDI Designer, muses about all things tech. Particularly: Notes on software development in Swift, Objective-C, and many non-Apple languages. Also: lots of random technology notes on OS X and iOS.

WeakArray for the Last Time (Swift 4.2)

I am obsessed with weak arrays. If you're trying to use multiple "protocolized" delegates, you need a weakly-held array of delegates.

This one started with this great article and expanded from there. Big thanks to Varindra Hart for the wise counsel as always.

@objc public protocol Equalable: class {
    @objc func isEqual(_ object: Any?) -> Bool
}

/// Store AnyObject subclasses weakly
/// * Note: if you wish to use a protocol, it must:
///   - be marked with `@objc`
///   - have all methods marked with `@objc`
///   - refine Equalable
public struct WeakArray<Element: Equalable> {
    private var items: [WeakBox<Element>] = []

    public init(_ elements: [Element]? = nil) {
        guard let elements = elements else { return }
        items = elements.map { WeakBox($0) }
    }

    public mutating func append(_ newElement: Element) {
        let box = WeakBox(newElement)
        items.append(box)
    }

    public mutating func remove(_ element: Element) {
        items.removeAll { item in
            return item.unbox?.isEqual(element) ?? false
        }
    }

    public var unboxed: [Element] {
        let filtered = items.filter { $0.unbox != nil }
        return filtered.compactMap { $0.unbox }
    }

    public var boxedCount: Int {
        return items.count
    }
}

extension WeakArray: Collection {
    public var startIndex: Int { return items.startIndex }
    public var endIndex: Int { return items.endIndex }

    public subscript(_ index: Int) -> Element? {
        return items[index].unbox
    }

    public func index(after idx: Int) -> Int {
        return items.index(after: idx)
    }
}

private final class WeakBox<T: Equalable> {
    weak var unbox: T?
    init(_ value: T) {
        unbox = value
    }
}

GitHub Gist

The need to have your protocol conform to @objc is a bug in Swift. See the StackOverflow question about this.

Modifiable: Modify and Assign without Temporary Variables

This Protocol & Extension allow you to assign and modify all in one line.

I've been meaning to write this for a long time. It's working in Swift 4.1. It's definitely a tiny bit tricky, definitely not necessary, and perhaps too cute to be worth it. But it is cute!

import UIKit

/// Modifiable, Dan Rosenstark 2018, inspired from:
/// https://medium.com/@victor.pavlychko/using-self-in-swift-class-extensions-6421dab02587
protocol Modifiable {}

extension NSObject: Modifiable {}

extension Modifiable {
    func modify(block: (Self)->()) -> Self {
        block(self)
        return self
    }
}

/// examples
let view = UIView().modify { $0.backgroundColor = .green }
print(view.backgroundColor == UIColor.green)

Xcodebuild: Valid Destinations on OSX?

To get a list of valid destinations, specify an erroneous key-value pair and xcodebuild will spit out the combinations that work.

List Destinations Command

xcodebuild test -destination 'platform=iOS Simulator' -workspace Register.xcworkspace -scheme ThatTestTarget

Output Example

Available destinations for the "ThatTestTarget" scheme:

{ platform:iOS Simulator, id:145A9B7E-B336-4819-8059-2FFEC408E05E, OS:11.1, name:iPad (5th generation) }
{ platform:iOS Simulator, id:69ABAF6F-ADA3-4E38-AC97-D71001447663, OS:9.3, name:iPad 2 }
{ platform:iOS Simulator, id:550E2F18-406D-4586-84BB-E48F1D704F27, OS:10.3.1, name:iPad Air }
{ platform:iOS Simulator, id:94734F1C-775F-40FA-9015-8196C08805EF, OS:11.1, name:iPad Air }
{ platform:iOS Simulator, id:1DB953DD-CD97-4EC7-8006-BCF01DF3E63F, OS:11.1, name:iPad Air 2 }
{ platform:iOS Simulator, id:DE3072DA-2E31-423D-9D77-220626F8B90A, OS:11.1, name:iPad Pro (9.7-inch) }
{ platform:iOS Simulator, id:3B5D18DB-13B5-4F28-B654-7D2ECDD1F6F0, OS:11.1, name:iPad Pro (10.5-inch) }
{ platform:iOS Simulator, id:A4225E3A-512C-4F42-ADD9-1E7E448C4D27, OS:11.1, name:iPad Pro (12.9-inch) }
{ platform:iOS Simulator, id:684FF1BA-8784-4B7C-B4E5-5231772F0FAC, OS:11.1, name:iPad Pro (12.9-inch) (2nd generation) }

Change Colons for Equals Signs, Remove Spaces, Ignore the ID

So if you want to use this destination:

platform:iOS Simulator, id:684FF1BA-8784-4B7C-B4E5-5231772F0FAC, OS:11.1, name:iPad Pro (12.9-inch) (2nd generation)

Change the colons for commas, remove the spaces, remove the ID, so you get this string:

platform=iOS Simulator,OS=11.1,name=iPad Pro (12.9-inch) (2nd generation)

Then the entire command would be:

xcodebuild test -destination 'platform=iOS Simulator,OS=11.1,name=iPad Pro (12.9-inch) (2nd generation)' -workspace Register.xcworkspace -scheme ThatTestTarget

From my StackOverflow Answer here

Square in Autolayout in Swift 3/4

Sometimes I marvel about how StackOverflow has been taken over by a zealous group of idiots who close questions at will. Here's today's example, and below is my answer, since I can't put it on the SO question itself.

extension UIView {
func constrainToSquareRelativeToView(_ view: UIView, multiplier: CGFloat = 1.0) {
translatesAutoresizingMaskIntoConstraints = false
let widthConstraint = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .lessThanOrEqual, toItem: view, attribute: .width, multiplier: multiplier, constant: 0)
widthConstraint.priority = .defaultLow
let heightConstraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: view, attribute: .height, multiplier: multiplier, constant: 0)
heightConstraint.priority = .defaultLow
let squareConstraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1.0, constant: 0)
let centerX = NSLayoutConstraint(item: self, attribute: .centerXWithinMargins, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: .centerXWithinMargins, multiplier: 1, constant:0)
let centerY = NSLayoutConstraint(item: self, attribute: .centerYWithinMargins, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: .centerYWithinMargins, multiplier: 1, constant:0)
view.addConstraints([widthConstraint, heightConstraint, squareConstraint, centerX, centerY])
}
}

Here's my result (inside which I'm playing with a UICollectionView, but that's another story):





How I Survive on Any Unix (Bash)

If you must type it all

export EDITOR=nano
PS1='\h:\w\$ '

If you can copy and paste:

export EDITOR=nano

function parse_git_dirty {
[[ $(git status 2> /dev/null | tail -n1) != "nothing to commit, working tree clean" ]] && echo "*"
}
function parse_git_branch {
git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e "s/* \(.*\)/(\1$(parse_git_dirty))/"
}
export PS1='\h:\[\033[1;33m\]\w\[\033[0m\]$(parse_git_branch)$ '


Solution for No Optionals in Swift String Interpolation

This is probably a bad idea, but it's fun to consider (for a brief moment).

// Solution is reduced from here

/// Anything that can be unwrapped.
public protocol Unwrappable {
/// Returns the unwrapped value of the receiver.
func unwrap() -> Any?
}

extension Optional: Unwrappable {
/// Returns the unwrapped value of the `Optional`.
public func unwrap() -> Any? {
switch self {
case nil:
return nil
case let unwrappable as Unwrappable:
return unwrappable.unwrap()
case let any:
return any
}
}
}


public extension String {
/// Creates an instance containing the unwrappable's representation.
init(stringInterpolationSegment expr: Unwrappable) {
if let unwrapped = expr.unwrap() {
self.init(stringInterpolationSegment: unwrapped)
} else {
self.init("nil")!
}
}
init(stringInterpolationSegment expr: Optional<T>) {
self.init(stringInterpolationSegment: expr as Unwrappable)
}
}

/**
Force the default behavior
*/
postfix operator *

public postfix func *(optional: Optional<Any>) -> String {
if "\(optional)" == "nil" {
return "nil"
}
return "Optional(\"\(optional)\")"
}


My Time/Datestamp Applescript

Here's the .scpt file that will open in Script Editor on Mac. You can set it up as a keystroke (CMD-Shift-D for me) in FastScripts (free for up to 10 scripts).

-- yar2050 (Dan Rosenstark)'s favorite date format
-- 2017-08-03 update
on fillInZero(int)
if (int < 10) then set int to "0" & int
return int as string
end fillInZero

set theDate to (current date)
set theHour to fillInZero(hours of (theDate) as integer)
set theMinutes to fillInZero(minutes of (theDate) as integer)
tell theDate to get (its month as integer)
set theMonth to fillInZero(result)
set theDay to fillInZero(day of theDate)
tell (theDate) to get (its year as integer) & "-" & (theMonth) & "-" & theDay & " " & (theHour) & ":" & theMinutes
set date_ to (result as string)
tell application "System Events"
set frontmostApplication to name of the first process whose frontmost is true
end tell


tell application frontmostApplication
delay 0.2 -- I have no idea why this is necessary
activate
tell application "System Events"
if (frontmostApplication = "Evernote") then
keystroke "h" using {command down, shift down}
keystroke "b" using {command down}
keystroke date_
keystroke "b" using {command down}
else
keystroke date_
end if
end tell
end tell







Just Because It Compiles: More Notes from Swift 3 Upgrade

This is the method signature for the superclass:

public func sendRequest<T: Request>(request: T, handler: (Result<T.Response, Error>) -> Void) -> NSURLSessionDataTask? {

and this is in the subclass

override func sendRequest<T : RequestType>(request: T, handler: (Result<T.Response, PayLib.Error>) -> Void) -> NSURLSessionDataTask? {

and in Swift 3 this got translated to be the same, except for some minor changes:

open func sendRequest<T: Request>(_ request: T, handler: @escaping (Result<T.Response, Error>) -> Void) -> URLSessionDataTask? {

Subclass:

override func sendRequest<T : RequestType>(_ request: T, handler: @escaping (Result<T.Response, PayLib.Error>) -> Void) -> URLSessionDataTask? ```

Now that was good enough for Swift 2. In Swift 3, a call to this method resulted in a crash with no interesting information.

The reason was the mismatch between RequestType and Request (RequestType is the protocol which the Request Protocol extends). So the fix was merely to have the signatures match exactly.

override func sendRequest<T : Request>(_ request: T, handler: @escaping (Result<T.Response, PayLib.Error>) -> Void) -> URLSessionDataTask? {

I'm not sure that there's a point here, except that: Just because Xcode likes it, it compiles and it works in Swift 2 doesn't mean that it won't cause a runtime crash in Swift 3. Or something like that: check everything by hand/eye!

Show Only Apps in Search Results, iOS 11

I want my iPad (and iPhone) to show Apps first when I hit CMD-space or slide down for search. This is the setting to change.



And this is what search results look like without all the clutter.