Skip to content

Instantly share code, notes, and snippets.

View rnapier's full-sized avatar

Rob Napier rnapier

View GitHub Profile
@rnapier
rnapier / MainActorRun.swift
Last active April 17, 2024 15:55
Regarding MainActor.run
// In regards to https://mastodon.social/@mattiem/112285978801305971
// MainActor class with synchronous methods
@MainActor final class M {
func methodA() {}
func methodB() {}
}
// Actor that relies on M.
actor A {
@rnapier
rnapier / SelectorNotifiction.swift
Last active April 16, 2024 22:19
Musings on Notifications and Actors
/// Some exploration into how selector-based notification interact with actors.
///
/// Or in the words of Brent Simmons (@brentsimmons@indieweb.social),
/// "Selector-based Notification Observers Are Actually Good"
/// Overall, I'm reasonably convinced, in that it avoids the headaches of `deinit` in actors.
/// However, Combine-based observation is also good at this, so I don't yet have a strong opinion
/// about old-school selectors vs Combine beyond my usual nervousness around Combine.
/// Whether "I'd like to reduce Combine" is more or less powerful than "I'd like to reduce @objc"
/// is yet to be seen.
@rnapier
rnapier / DefaultValueConcurrency.swift
Last active April 12, 2024 01:30
Somewhat surprising impact of non-Sendable default values
// With Strict Concurrency
class C {}
struct S {
func f() {
Task { // Surprisingly need `@MainActor in` here to make this correct
await g() // Warning: Passing argument of non-sendable type 'C' into main actor-isolated context may introduce data races
}
}
@rnapier
rnapier / TestVO.swift
Created July 31, 2023 20:04
Demonstration of VoiceOver bug
// Demonstration of FB12811151
// On some devices (so far we haven't found any newer than an 2020 SE2), VoiceOver will allow you to select
// the link by swiping down, and then will immediately re-select the entire text. The user generally cannot
// follow the link because it snaps back too quickly.
//
// On one iPhone 8, this did not reproduce until the device went to sleep, then it reliably reproduced.
import SwiftUI
let text = try! AttributedString(markdown: "Here is a link to [ArchiveOrg](https://archive.org). It should be selectable with the rotor without jumping back to the whole text.")
import Foundation
public class Disposable {
private var isDisposed = false
private let _dispose: () -> Void
public func dispose() {
if !isDisposed {
_dispose()
isDisposed = true
}
@rnapier
rnapier / GridView.swift
Last active September 27, 2020 20:32
GridView that makes me cry
// This GridView makes me cry. It is recreating an HTML-style bordered table, sized to
// its data, with a header. It requires a GeometryReader and Preferences, which might
// be unavoidable, but it also requires a *horrible* DispatchQueue.main.async in updateMaxValue.
// This means it doesn't work in Previews, and completely breaks the idea of "declarative" UI.
import SwiftUI
struct WidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
struct LengthLimitedTextField: UIViewRepresentable {
var title: String
@Binding var text: String
var maxLength: Int
var onCommit: () -> Void
init(_ title: String, text: Binding<String>, maxLength: Int = 255, onCommit: @escaping () -> Void) {
self.title = title
self._text = text
self.maxLength = maxLength
// Based on https://twitter.com/ravibastola/status/1249555595285291008?s=20
extension Bundle {
// This is synchronous, which is bad if called on the main queue.
func decodeJSON<T: Decodable>(_ type: T.Type = T.self, from filename: String) throws -> T {
guard let path = self.url(forResource: filename, withExtension: nil) else {
throw NSError(domain: NSCocoaErrorDomain,
code: CocoaError.fileNoSuchFile.rawValue,
userInfo: [NSFilePathErrorKey: filename])
}
//
// TextStyle.swift
//
// Created by Rob Napier on 12/20/19.
// Copyright © 2019 Rob Napier. All rights reserved.
//
import SwiftUI
public struct TextStyle {
@rnapier
rnapier / assertion.swift
Last active August 21, 2020 14:00
AssertionFailure as a type
///
/// Boring setup. See below for the good parts
///
public class Logger {
public static let root = Logger(subsystem: .none, parent: nil)
public let subsystem: Subsystem
public let parent: Logger?
public init(subsystem: Subsystem, parent: Logger? = .root) {
self.subsystem = subsystem