Skip to content

Instantly share code, notes, and snippets.

@leviathan
Last active June 10, 2022 07:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leviathan/66a0f1d5697754823efad1a3a5b37f95 to your computer and use it in GitHub Desktop.
Save leviathan/66a0f1d5697754823efad1a3a5b37f95 to your computer and use it in GitHub Desktop.

Swift code snippets

Make every view scollable

You can constrain the content, the UIStackView, to the contentLayoutGuide top, bottom, leading, and trailing to define the scrollable area. You can then constrain the UIStackView’s width to the frameLayoutGuide so that it only scrolls vertically. That’s it—your content can now scroll as it shrinks or grows!

UIView update constraints

final class SomeView: UIView {

    private func setupView() {
        ...
        setupActions()
    }
    
    //we add a top constraint property
    private var headerViewTop: NSLayoutConstraint!
    
    private func setupLayout() {
        headerViewTop = headerView.topAnchor.constraint(equalTo: topAnchor)
            ...
    }
    
    private func setupActions() {
        addButton.addTarget(self, action: #selector(moveHeaderView), for: .touchUpInside)
    }
    
    @objc 
    private func moveHeaderView() {
        //here we have 2 ways to modify the constraint
    
        //first one (easier & preffered)
        //manual trigger layout cycle
        headerViewTop.constant += 10
        setNeedsLayout()
    
        //second one (use for performance boost)
        headerViewTopConstant += 10
    }
    
    //introduce a new variable for updateConstraints logic
    private var headerViewTopConstant: CGFloat = 0 {
        didSet {
            //manual trigger layout cycle, but here
            //we will set the constant inside updateConstraints
            setNeedsUpdateConstraints()
        }
    }
    
    override func updateConstraints() {
        headerViewTop.constant = headerViewTopConstant
        super.updateConstraints()
    }
}

Thread safe access

A generic class, ThreadSafe, that will hold any value and provide a thread-safe way to access that value.

final class ThreadSafe<A> {
    private var _value: A
    private let queue = DispatchQueue(label: "ThreadSafe")
    init(_ value: A) {
        self._value = value
    }
    
    var value: A {
        return queue.sync { _value }
    }
    
    func atomically(_ transform: (inout A) -> ()) {
        queue.sync {
            transform(&self._value)
        }
    }
}

extension Array {
    func concurrentMap<B>(_ transform: @escaping (Element) -> B) -> [B] {
        let result = ThreadSafe(Array<B?>(repeating: nil, count: count))
        DispatchQueue.concurrentPerform(iterations: count) { idx in
            let element = self[idx]
            let transformed = transform(element)
            result.atomically {
                $0[idx] = transformed
            }
        }
        return result.value.map { $0! }
    }
}

Measure time consumed by a closure

/// Measures how long a closure takes to complete
/// Examples: `timeElapsed { sleep(2.2) } // 2.20000`
///
func timeElapsed(_ closure: () -> Void) -> Double {
    let start = DispatchTime.now()
    closure()
    let end = DispatchTime.now()
    let diff = end.uptimeNanoseconds - start.uptimeNanoseconds
    return Double(diff) // 1_000_000_000
}

see: https://github.com/eonist/TimeMeasure

time {
    let pointCounts = urls.map { url in
        Parser(url: url)!.points.count
    }
    print(pointCounts.reduce(0, +))
}
/*
44467
1.322891076 sec
*/

Random sleep

Sleep for a random amount of time between 1 and 7 seconds. (Great for simulating async network calls etc)

sleep((1..<7).randomElement()!)

Make rounded graphics look great

Use NSScreen.main.backingScaleFactor for macOS and UIScreen.main.scale for iOS. This ensures that rounded graphics looks sharp.

// iOS
view.layer.rasterizationScale
view.layer.shouldRasterize = true

// macOS
self.caLayer?.rasterizationScale = 2.0 * Screen.mainScreenScale
self.caLayer?.shouldRasterize = true

CADisplayLink timer

/// A timer object that allows to synchronize drawing to the refresh rate of the display.
/// New event triggers will occur right after a frame is getting rendered by the connected display.
///
/// See: https://github.com/DmIvanov/Animations/blob/master/Animations/AnimationView.swift
/// See: https://github.com/DmIvanov/Animations/blob/master/Animations/AnimationLayer.swift
final class ScreenLink {
    // Basic time-related properties of CADisplayLink are timestamp, targetTimestamp and duration.
    // Timestamp is the time of the last displayed frame, the starting point of your calculation.
    // TargetTimestamp is the time of the next frame to trigger the link.
    // And duration is the time interval between two frames — it’s constant for the link.
    private var displayLink: CADisplayLink?

    init() {
    }

    // MARK: Public

    func start() {
        stop()
        displayLink = CADisplayLink(target: self, selector: #selector(displayRefreshed(displayLink:)))
        displayLink?.add(to: .current, forMode: .default)
        // displayLink.add(to: .main, forMode: .default)
    }

    func stop() {
        displayLink?.isPaused = true
        displayLink?.invalidate()
    }

    // MARK: Private

    @objc private func displayRefreshed(displayLink: CADisplayLink) {
        print(displayLink.timestamp)

        let pixel: CGFloat = .zero
        let pixelMatrix: [CGFloat] = []
        for pixel in pixelMatrix {
            // some calculation here
            if (CACurrentMediaTime() >= displayLink.targetTimestamp) {
                // no more time
                break
            }
        }
        // setNeedsDisplay()
    }
}

Make methods off-limit

https://www.mokacoding.com/blog/swift-unavailable-how-to/

// Swift 3
@available(<platform>, unavailable: <version>, message: <message>)

This is a great way to avoid having to repeat this method in every subclass that uses UIView.

/// Note how the platform parameter has * value, which means any platform, 
/// and unavailable has no version value, which means that the methods is 
/// unavailable regardless of the current version.
@available(*, unavailable)
public required init?(coder: NSCoder) {
   fatalError("init?(coder:) is not supported")
}

Simplify similar code with Closure

let closure = { (text: String, bgColor: UIColor, y: CGFloat, action: String) in
   let btn: UIButton = .init(type: .system)
   btn.backgroundColor = bgColor
   btn.setTitle(text, for: .normal)
   btn.titleLabel?.font =  .systemFont(ofSize: 12)
   btn.frame = .init(x:00, y:y, width:100, height:50)
   btn.addTarget(self, action: Selector(action), for: .touchUpInside)
   self.addSubview(btn)
}
// btn1
closure(
   "Forward",
   .gray,
   250,
   "onForwardButtonClick"
)
// btn2
closure(
   "Back",
   .lightGray,
   250,
   "onbackButtonClick"
)

Combine

Scan

/// Button "tap" counter with scan
let myButton = UIButton()
myButton.onTap
    .scan(0) { lastCount, newValue in
        lastCount + 1
    }
    .sink { next in
        print("Button tapped \(next) times")
    }
    
/// You have a sequence of Int values: [0, 1, 2, 3, 4, 5, 6] and want to always get the last 3
/// each time a new value is emitted.
/// 
let numbers = Just([0, 1, 2, 3, 4, 5, 6])

numbers.scan([]) { lastSlice, newValue in
    Array(lastSlice + [newValue]).suffix(3)
}
.sink { value in
    print("last 3: \(value)")
}
// some funky swift code block here...
let encodedDataFromString = Data("funky string".utf8)
let decodedStringFromData = String(decoding: encodedDataFromString, using: UTF8.self) 
/// Decode `String` to Model
let decoder = try JSONDecoder().decode(Foo.self, from: Data("""
    {
      "array": [1, "2", 3],
      "value": "invalid",
    }
    """.utf8))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment