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!
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()
}
}
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! }
}
}
/// Measures how long a closure takes to complete
/// Examples: `timeElapsed { sleep(2.2) } // 2.20000`
///
func timeElapsed(_ closure: () -> Void) -> Double {
let nanoToSecond: Double = 1_000_000_000
let start = DispatchTime.now()
closure()
let end = DispatchTime.now()
let diff = end.uptimeNanoseconds - start.uptimeNanoseconds
return Double(diff) / nanoToSecond
}
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
*/
Sleep for a random amount of time between 1 and 7 seconds. (Great for simulating async network calls etc)
sleep((1..<7).randomElement()!)
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
/// 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()
}
}
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")
}
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"
)
/// 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))