Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into kve/the-label-but-sma…
Browse files Browse the repository at this point in the history
…ller

* origin/main: (62 commits)
  AccessibilityBlocker aggressively blocking. (#483)
  Bumping version to 3.0.0 (#482)
  Allow for customization of the preview name (#478)
  Update CHANGELOG for AttributedLabel fixes (#480)
  Fix link detection for stretched labels (#476)
  chore: Updated minimum deployment target from iOS 14 to iOS 15 [UI-5185] (#479)
  chore(ios): Bump to Xcode 15.1 and Ruby 3.2.2 [UI-5184] (#477)
  AXCustomContent Support (#471)
  Bumping versions to 2.2.0 (#470)
  Update concatenation logic and unit tests
  update changelog
  never cache subelements
  optionally do not cache subelements
  Feature: add TintAdjustmentMode, modifiers, and tests
  Add to CHANGELOG
  Add tintAdjustmentMode to Image
  Bumping versions to 2.1.0 (#466)
  Resolved a Swift 5.9 compilation warning (#465)
  Update KeyboardObserver (#463)
  Bump activesupport from 7.0.4.3 to 7.0.7.2
  ...
  • Loading branch information
kyleve committed Mar 16, 2024
2 parents b9905f4 + c46b5ef commit e43844d
Show file tree
Hide file tree
Showing 450 changed files with 5,875 additions and 1,578 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
jobs:
build:
name: Generate docs with jazzy and publish to Github pages
runs-on: macos-12
runs-on: macos-13-xlarge

steps:
- name: Checkout
Expand All @@ -22,7 +22,7 @@ jobs:
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ env.ruby_version }}
# Uses version specified in .ruby_version
bundler-cache: true # runs 'bundle install' and caches installed gems automatically

- name: Generate Docs
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/env.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
xcode_version=14.2
ruby_version=2.7
xcode_version=15.1
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
jobs:
lint:
name: Lint Swift code with SwiftFormat
runs-on: macos-12
runs-on: macos-13-xlarge

steps:
- name: Checkout
Expand Down
28 changes: 17 additions & 11 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,25 @@ jobs:
spm:
name: "iOS ${{ matrix.sdk }}"

runs-on: macos-12
runs-on: macos-13-xlarge

strategy:
fail-fast: false # Don’t fail-fast so that we get all snapshot test changes
matrix:
include:
- sdk: "14.5"
destination: platform=iOS Simulator,OS=14.5,name=iPad (8th generation)

- sdk: "15.0"
destination: platform=iOS Simulator,OS=15.0,name=iPhone 8
- sdk: "15.4"
destination: platform=iOS Simulator,OS=15.4,name=iPhone SE (3rd generation)
installation_required: true

- sdk: "16.2"
destination: platform=iOS Simulator,OS=16.2,name=iPhone 8
destination: platform=iOS Simulator,OS=16.2,name=iPhone SE (3rd generation)
installation_required: true

- sdk: "17.2"
destination: platform=iOS Simulator,OS=17.2,name=iPhone SE (3rd generation)
# The iOS 17.2 SDK is pre-installed on the macOS 13 image.
# Attempting to install it will fail with an error.
installation_required: false

steps:
- uses: actions/checkout@v1
Expand All @@ -35,9 +40,10 @@ jobs:
run: sudo xcode-select -s /Applications/Xcode_${{ env.xcode_version }}.app

- name: Install xcodes
run: brew install aria2 robotsandpencils/made/xcodes
run: brew install aria2 xcodesorg/made/xcodes

- name: Install iOS ${{ matrix.sdk }}
if: ${{ matrix.installation_required }}
run: sudo xcodes runtimes install "iOS ${{ matrix.sdk }}"

- name: Build & Test
Expand All @@ -60,7 +66,7 @@ jobs:
cocoapods:
name: "CocoaPods"

runs-on: macos-12
runs-on: macos-13-xlarge

steps:
- uses: actions/checkout@v1
Expand All @@ -74,7 +80,7 @@ jobs:
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ env.ruby_version }}
# Uses version specified in .ruby_version
bundler-cache: true # runs 'bundle install' and caches installed gems automatically

- name: Pod Install
Expand All @@ -83,4 +89,4 @@ jobs:
- name: Build & Test
run: |
xcodebuild -workspace SampleApp/SampleApp.xcworkspace -scheme "BlueprintUI-Unit-Tests" -destination "platform=iOS Simulator,OS=16.2,name=iPhone 8"
xcodebuild -workspace SampleApp/SampleApp.xcworkspace -scheme "BlueprintUI-Unit-Tests" -destination "platform=iOS Simulator,OS=17.2,name=iPhone SE (3rd generation)"
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.2.2
2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.7
5.9
5 changes: 5 additions & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
--shortoptionals always
--voidtype void
--wraparguments before-first
--ifdef no-indent
--header strip
--someAny disabled # opaqueGenericParameters

--rules anyObjectProtocol
--rules blankLinesBetweenScopes
Expand All @@ -24,9 +27,11 @@
--rules emptyBraces
--rules extensionAccessControl
--rules indent
--rules fileHeader
--rules leadingDelimiters
--rules linebreakAtEndOfFile
--rules modifierOrder
--rules opaqueGenericParameters
--rules redundantGet # https://google.github.io/swift/#properties-1
--rules redundantInit # https://google.github.io/swift/#initializers-1
--rules redundantParens # https://google.github.io/swift/#parentheses, https://google.github.io/swift/#enum-cases, https://google.github.io/swift/#trailing-closures
Expand Down
5 changes: 4 additions & 1 deletion BlueprintUI.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Pod::Spec.new do |s|

s.swift_version = SWIFT_VERSION

s.ios.deployment_target = '14.0'
s.ios.deployment_target = BLUEPRINT_IOS_DEPLOYMENT_TARGET

s.source_files = 'BlueprintUI/Sources/**/*.swift'

Expand All @@ -27,5 +27,8 @@ Pod::Spec.new do |s|
test_spec.library = 'swiftsimd'
test_spec.source_files = 'BlueprintUI/Tests/**/*.swift'
test_spec.framework = 'XCTest'
test_spec.pod_target_xcconfig = {
'APPLICATION_EXTENSION_API_ONLY' => 'NO',
}
end
end
2 changes: 1 addition & 1 deletion BlueprintUI/Sources/AttributedText/AttributedText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import UIKit
self.mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
}

public func range<StringType: StringProtocol>(of aString: StringType) -> Range<String.Index>? {
public func range(of aString: some StringProtocol) -> Range<String.Index>? {
string.range(of: aString)
}

Expand Down
110 changes: 97 additions & 13 deletions BlueprintUI/Sources/BlueprintView/BlueprintView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public final class BlueprintView: UIView {

private let rootController: NativeViewController

private var layoutResult: LayoutResultNode?

private var sizesThatFit: [SizeConstraint: CGSize] = [:]

/// A base environment used when laying out and rendering the element tree.
Expand Down Expand Up @@ -100,6 +102,16 @@ public final class BlueprintView: UIView {
}
}

/// An optional explicit layout mode for this view. If `nil`, this view will inherit the layout
/// mode of its nearest ancestor or use ``LayoutMode/default`` if it has no ancestor.
public var layoutMode: LayoutMode? {
didSet {
if layoutMode != oldValue {
setNeedsViewHierarchyUpdate()
}
}
}

/// An optional name to help identify this view
public var name: String?

Expand Down Expand Up @@ -144,6 +156,15 @@ public final class BlueprintView: UIView {
addSubview(rootController.view)
setContentHuggingPriority(.defaultHigh, for: .horizontal)
setContentHuggingPriority(.defaultHigh, for: .vertical)

NotificationCenter
.default
.addObserver(
self,
selector: #selector(defaultLayoutModeChanged(notification:)),
name: .defaultLayoutModeChanged,
object: nil
)
}

public convenience override init(frame: CGRect) {
Expand Down Expand Up @@ -211,11 +232,19 @@ public final class BlueprintView: UIView {
)
defer { Logger.logSizeThatFitsEnd(view: self) }

let measurement = element.content.measure(
in: constraint,
environment: makeEnvironment(),
cache: CacheFactory.makeCache(name: cacheName)
)
let environment = makeEnvironment()
let layoutMode = environment.layoutMode
let renderContext = RenderContext(layoutMode: layoutMode)

// Wrap in a RenderContext to ensure any out-of-band operations inherit the same context.
let measurement = renderContext.perform {
element.content.measure(
in: constraint,
environment: environment,
cacheName: cacheName,
layoutMode: layoutMode
)
}

sizesThatFit[constraint] = measurement

Expand Down Expand Up @@ -339,10 +368,21 @@ public final class BlueprintView: UIView {
size: bounds.size + rootCorrection.size
)

/// Grab view descriptions
let viewNodes = element?
.layout(layoutAttributes: LayoutAttributes(frame: rootFrame), environment: environment)
.resolve() ?? []
let layoutMode = environment.layoutMode
let renderContext = RenderContext(layoutMode: layoutMode)

// Perform layout.
// Wrap in a RenderContext to ensure any out-of-band operations inherit the same context.
layoutResult = renderContext.perform {
element?.layout(
frame: rootFrame,
environment: environment,
layoutMode: layoutMode
)
}

// Flatten into tree of view descriptions
let viewNodes = layoutResult?.resolve() ?? []

let layoutEndTime = CACurrentMediaTime()
Logger.logLayoutEnd(view: self)
Expand Down Expand Up @@ -370,20 +410,25 @@ public final class BlueprintView: UIView {
)
)

hasUpdatedViewHierarchy = true
isInsideUpdate = false

/// We intentionally deliver our lifecycle callbacks (eg, `onAppear`,
/// `onDisappear`, etc, _after_ we've marked our view as updated.
/// This is in case the `onAppear` callback triggers a re-render,
/// we don't hit our recurisve update precondition.

for callback in updateResult.lifecycleCallbacks {
callback()
}

Logger.logViewUpdateEnd(view: self)
let viewUpdateEndTime = CACurrentMediaTime()

hasUpdatedViewHierarchy = true

isInsideUpdate = false

metricsDelegate?.blueprintView(
self,
completedRenderWith: .init(
layoutMode: layoutMode,
totalDuration: viewUpdateEndTime - startTime,
layoutDuration: layoutEndTime - startTime,
viewUpdateDuration: viewUpdateEndTime - layoutEndTime
Expand All @@ -401,6 +446,27 @@ public final class BlueprintView: UIView {
return rootController.children
}

/// Forces a synchronous layout, for testing purposes.
@_spi(BlueprintDebugging)
public func forceSynchronousLayout() {
setNeedsViewHierarchyUpdate()
updateViewHierarchyIfNeeded()
}

/// Dumps the result of the most recent layout, by recursing through the layout tree and calling
/// the provided visitor on each node. By default, this prints to `stdout`.
@_spi(BlueprintDebugging)
public func dumpLayoutResult(
visit: ((_ depth: Int, _ identifier: String, _ frame: CGRect) -> Void) = { depth, identifier, frame in
let origin = "x \(frame.origin.x), y \(frame.origin.y)"
let size = "\(frame.size.width) × \(frame.size.height)"
let indent = String(repeating: " ", count: depth)
print("\(indent)\(identifier) \(origin), \(size)")
}
) {
layoutResult?.dump(visit: visit)
}

private func makeEnvironment() -> Environment {

let inherited: Environment = {
Expand Down Expand Up @@ -429,6 +495,10 @@ public final class BlueprintView: UIView {
environment.windowSize = window.bounds.size
}

if let layoutMode = layoutMode ?? RenderContext.current?.layoutMode {
environment.layoutMode = layoutMode
}

return environment
}

Expand All @@ -443,6 +513,15 @@ public final class BlueprintView: UIView {
node.onDisappear?()
}
}

@objc
private func defaultLayoutModeChanged(notification: Notification) {
// if this view has an explicit mode set we can ignore this
guard layoutMode == nil else { return }

// otherwise we should re-render
setNeedsViewHierarchyUpdate()
}
}


Expand All @@ -456,10 +535,15 @@ public protocol BlueprintViewMetricsDelegate: AnyObject {

public struct BlueprintViewRenderMetrics {

/// The layout mode used to render the view.
public var layoutMode: LayoutMode

/// The total time it took to apply a new element.
public var totalDuration: TimeInterval

/// The time it took to lay out and measure the new element.
public var layoutDuration: TimeInterval

/// The time it took to update the on-screen views for the element.
public var viewUpdateDuration: TimeInterval
}
Expand Down
Loading

0 comments on commit e43844d

Please sign in to comment.