From 06bfa0c585677a881e4e053183b2780095bf447a Mon Sep 17 00:00:00 2001 From: John Szumski Date: Wed, 29 May 2024 13:22:56 -0400 Subject: [PATCH 1/7] Conform data structs to Sendable so they can be used in static properties --- Paralayout/Angle.swift | 2 +- Paralayout/AspectRatio.swift | 2 +- Paralayout/Interpolation.swift | 2 +- Paralayout/UIView+Sizing.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Paralayout/Angle.swift b/Paralayout/Angle.swift index 4d9b716..ae41c9b 100644 --- a/Paralayout/Angle.swift +++ b/Paralayout/Angle.swift @@ -16,7 +16,7 @@ import CoreGraphics -public struct Angle: Comparable { +public struct Angle: Comparable, Sendable { // MARK: - Public Static Properties diff --git a/Paralayout/AspectRatio.swift b/Paralayout/AspectRatio.swift index a42684e..136ae72 100644 --- a/Paralayout/AspectRatio.swift +++ b/Paralayout/AspectRatio.swift @@ -17,7 +17,7 @@ import UIKit /// A value type representing the ratio between a width and a height. -public struct AspectRatio: Comparable, CustomDebugStringConvertible { +public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { // MARK: - Public Static Properties diff --git a/Paralayout/Interpolation.swift b/Paralayout/Interpolation.swift index 81a2143..b2ead9a 100644 --- a/Paralayout/Interpolation.swift +++ b/Paralayout/Interpolation.swift @@ -102,7 +102,7 @@ public enum Clamp { // MARK: - -public struct Interpolation: Comparable { +public struct Interpolation: Comparable, Sendable { // MARK: - Public Types diff --git a/Paralayout/UIView+Sizing.swift b/Paralayout/UIView+Sizing.swift index 465a6f7..b782393 100644 --- a/Paralayout/UIView+Sizing.swift +++ b/Paralayout/UIView+Sizing.swift @@ -21,7 +21,7 @@ extension UIView { // MARK: - Public Types /// Constraints on the result of a call to `sizeThatFits(_:)`. - public struct SizingConstraints: OptionSet { + public struct SizingConstraints: OptionSet, Sendable { // MARK: - Life Cycle From 020565571564b1dee60cf259724fdea5dbea27ea Mon Sep 17 00:00:00 2001 From: John Szumski Date: Wed, 29 May 2024 13:28:26 -0400 Subject: [PATCH 2/7] Avoid a static logger instance because it isn't marked Sendable, instead make a new one using static constants for subsystem and category. --- Paralayout/UIView+Alignment.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Paralayout/UIView+Alignment.swift b/Paralayout/UIView+Alignment.swift index affe041..cf78b31 100644 --- a/Paralayout/UIView+Alignment.swift +++ b/Paralayout/UIView+Alignment.swift @@ -170,7 +170,8 @@ extension AlignmentContext { // MARK: - -private let ParalayoutLog = OSLog(subsystem: "com.squareup.Paralayout", category: "layout") +private let ParalayoutLogSubsystem = "com.squareup.Paralayout" +private let ParalayoutLogCategory = "layout" /// Triggered when an alignment method is called that uses mismatched position types, i.e. aligning a view's leading or /// trailing edge to another view's left or right edge, or vice versa. This type of mismatch is likely to look correct @@ -178,7 +179,7 @@ private let ParalayoutLog = OSLog(subsystem: "com.squareup.Paralayout", category private func ParalayoutAlertForMismatchedAlignmentPositionTypes() { os_log( "%@", - log: ParalayoutLog, + log: OSLog(subsystem: ParalayoutLogSubsystem, category: ParalayoutLogCategory), type: .default, """ Paralayout detected an alignment with mismatched position types. Set a symbolic breakpoint for \ @@ -193,7 +194,7 @@ private func ParalayoutAlertForMismatchedAlignmentPositionTypes() { private func ParalayoutAlertForInvalidViewHierarchy() { os_log( "%@", - log: ParalayoutLog, + log: OSLog(subsystem: ParalayoutLogSubsystem, category: ParalayoutLogCategory), type: .default, """ Paralayout detected an alignment with an invalid view hierarchy. The views involved in alignment calls must \ From da34c781df4172b7c39e3ac3b6b22b154e291098 Mon Sep 17 00:00:00 2001 From: John Szumski Date: Wed, 29 May 2024 13:30:27 -0400 Subject: [PATCH 3/7] Mark protocols with MainActor so they can be applied to UIKit types and adjust all call sites to match --- .../ViewAlignmentSnapshotTests.swift | 12 ++++++++++ Paralayout/Alignable.swift | 4 ++++ Paralayout/AspectRatio.swift | 21 ++++++++-------- Paralayout/LayoutDirection.swift | 1 + Paralayout/PixelRounding.swift | 24 ++++++++++--------- Paralayout/UIFont+CapInsets.swift | 2 +- Paralayout/UILabel+Alignment.swift | 1 + Paralayout/UIView+Alignment.swift | 4 ++-- Paralayout/UIView+AlignmentConveniences.swift | 14 +++++------ Paralayout/ViewDistributionItem.swift | 5 ++-- ParalayoutTests/AspectRatioTests.swift | 5 ++++ ParalayoutTests/DistributionTests.swift | 3 +++ ParalayoutTests/PixelRoundingTests.swift | 8 +++++++ ParalayoutTests/UIViewFrameTests.swift | 14 +++++++++++ ParalayoutTests/UIViewSizingTests.swift | 12 ++++++++++ ParalayoutTests/ViewArrayBuilderTests.swift | 8 +++++++ .../ViewDistributionBuilderTests.swift | 8 +++++++ 17 files changed, 113 insertions(+), 33 deletions(-) diff --git a/Example/ParalayoutSnapshotTests/ViewAlignmentSnapshotTests.swift b/Example/ParalayoutSnapshotTests/ViewAlignmentSnapshotTests.swift index e9250ba..a25c951 100644 --- a/Example/ParalayoutSnapshotTests/ViewAlignmentSnapshotTests.swift +++ b/Example/ParalayoutSnapshotTests/ViewAlignmentSnapshotTests.swift @@ -19,6 +19,7 @@ import SnapshotTesting final class ViewAlignmentSnapshotTests: SnapshotTestCase { + @MainActor func testSiblingAlignment() { let containerView = UIView(frame: .init(x: 0, y: 0, width: 200, height: 200)) containerView.backgroundColor = .white @@ -31,6 +32,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { secondSubview.backgroundColor = .red containerView.addSubview(secondSubview) + @MainActor func verifySnapshot( receiverPosition: Position, targetPosition: Position, @@ -80,6 +82,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { verifySnapshot(receiverPosition: .center, targetPosition: .topRight, verticalOffset: -15) } + @MainActor func testLayoutDirection() { let containerView = UIView(frame: .init(x: 0, y: 0, width: 100, height: 100)) containerView.backgroundColor = .white @@ -140,6 +143,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: [])) } + @MainActor func testTransformHasNoEffect() { let containerView = UIView(frame: .init(x: 0, y: 0, width: 100, height: 100)) containerView.backgroundColor = .white @@ -152,6 +156,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { secondSubview.backgroundColor = .red containerView.addSubview(secondSubview) + @MainActor func verifySnapshot( receiverTransform: CGAffineTransform, targetTransform: CGAffineTransform, @@ -185,6 +190,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { verifySnapshot(receiverTransform: .identity, targetTransform: .init(scaleX: 2, y: 3)) } + @MainActor func testNonZeroBoundsOrigin() { let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) containerView.backgroundColor = .white @@ -213,6 +219,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: [])) } + @MainActor func testAlignmentWithLayoutMargins() { let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) containerView.backgroundColor = .white @@ -246,6 +253,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: ["bothLayoutMargins"])) } + @MainActor func testAlignmentUsingCapInsets() { let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 100)) containerView.backgroundColor = .white @@ -269,6 +277,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: [])) } + @MainActor func testAlignmentUsingFirstLine() { let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 100)) containerView.backgroundColor = .white @@ -299,6 +308,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: [])) } + @MainActor func testAlignmentUsingFirstLineCapInsets() { let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 100)) containerView.backgroundColor = .white @@ -329,6 +339,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: [])) } + @MainActor func testAlignmentWithRect() { let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) containerView.backgroundColor = .white @@ -365,6 +376,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: [])) } + @MainActor func testAlignmentWithFrame() { let targetTransform = CGAffineTransform(translationX: -20, y: 10) let receiverTransform = CGAffineTransform(scaleX: 0.8, y: 0.8) diff --git a/Paralayout/Alignable.swift b/Paralayout/Alignable.swift index 5595602..baaeabd 100644 --- a/Paralayout/Alignable.swift +++ b/Paralayout/Alignable.swift @@ -19,10 +19,12 @@ import UIKit /// Describes an object that can participate in alignment. In practice, this represents a view. public protocol Alignable { + @MainActor var alignmentContext: AlignmentContext { get } } +@MainActor public struct AlignmentContext { // MARK: - Life Cycle @@ -47,6 +49,7 @@ public struct AlignmentContext { extension UIView: Alignable { + @MainActor public var alignmentContext: AlignmentContext { return AlignmentContext(view: self, alignmentBounds: bounds) } @@ -168,6 +171,7 @@ public struct FrameAlignmentProxy: Alignable { // MARK: - Private Methods + @MainActor private func withViewInSuperview(view: UIView, perform: (_ superview: UIView) -> T) -> T { if let superview = view.superview { return perform(superview) diff --git a/Paralayout/AspectRatio.swift b/Paralayout/AspectRatio.swift index 136ae72..29f3e4e 100644 --- a/Paralayout/AspectRatio.swift +++ b/Paralayout/AspectRatio.swift @@ -105,7 +105,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// /// - parameter width: The desired width. /// - parameter scaleFactor: The view/window/screen to use for pixel rounding. - public func height(forWidth width: CGFloat, in scaleFactor: ScaleFactorProviding) -> CGFloat { + @MainActor public func height(forWidth width: CGFloat, in scaleFactor: ScaleFactorProviding) -> CGFloat { return (ratioHeight * width / ratioWidth).roundedToPixel(in: scaleFactor) } @@ -113,7 +113,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// /// - parameter height: The desired height. /// - parameter scaleFactor: The view/window/screen to use for pixel rounding. - public func width(forHeight height: CGFloat, in scaleFactor: ScaleFactorProviding) -> CGFloat { + @MainActor public func width(forHeight height: CGFloat, in scaleFactor: ScaleFactorProviding) -> CGFloat { return (ratioWidth * height / ratioHeight).roundedToPixel(in: scaleFactor) } @@ -122,7 +122,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// /// - parameter width: The desired width. /// - parameter scaleFactor: The view/window/screen to use for pixel rounding. - public func size(forWidth width: CGFloat, in scaleFactor: ScaleFactorProviding) -> CGSize { + @MainActor public func size(forWidth width: CGFloat, in scaleFactor: ScaleFactorProviding) -> CGSize { return CGSize( width: width, height: height(forWidth: width, in: scaleFactor) @@ -134,7 +134,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// /// - parameter height: The desired height. /// - parameter scaleFactor: The view/window/screen to use for pixel rounding. - public func size(forHeight height: CGFloat, in scaleFactor: ScaleFactorProviding) -> CGSize { + @MainActor public func size(forHeight height: CGFloat, in scaleFactor: ScaleFactorProviding) -> CGSize { return CGSize( width: width(forHeight: height, in: scaleFactor), height: height @@ -147,7 +147,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// - parameter size: The bounding size. /// - parameter scaleFactor: The view/window/screen to use for pixel alignment. /// - returns: A size with the receiver's aspect ratio, no larger than the bounding size. - public func size(toFit size: CGSize, in scaleFactor: ScaleFactorProviding) -> CGSize { + @MainActor public func size(toFit size: CGSize, in scaleFactor: ScaleFactorProviding) -> CGSize { if size.aspectRatio <= self { // Match width, narrow the height. let fitHeight = min(size.height, height(forWidth: size.width, in: scaleFactor)) @@ -169,7 +169,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// - parameter scaleFactor: The view/window/screen to use for pixel alignment. /// - parameter layoutDirection: The effective layout direction of the view in which the `rect` is defined. /// - returns: A rect with the receiver's aspect ratio, strictly within the bounding rect. - public func rect( + @MainActor public func rect( toFit rect: CGRect, at position: Position, in scaleFactor: ScaleFactorProviding, @@ -193,7 +193,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// - parameter context: The view/window/screen that provides the scale factor and effective layout direction in /// which the rect should be positioned. /// - returns: A rect with the receiver's aspect ratio, strictly within the bounding rect. - public func rect( + @MainActor public func rect( toFit rect: CGRect, at position: Position, in context: (ScaleFactorProviding & LayoutDirectionProviding) @@ -212,7 +212,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// - parameter size: The bounding size. /// - parameter scaleFactor: The view/window/screen to use for pixel alignment. /// - returns: A size with the receiver's aspect ratio, at least as large as the bounding size. - public func size(toFill size: CGSize, in scaleFactor: ScaleFactorProviding) -> CGSize { + @MainActor public func size(toFill size: CGSize, in scaleFactor: ScaleFactorProviding) -> CGSize { if size.aspectRatio <= self { // Match height, expand the width. let fillWidth = width(forHeight: size.height, in: scaleFactor) @@ -234,7 +234,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// - parameter scaleFactor: The view/window/screen to use for pixel alignment. /// - parameter layoutDirection: The effective layout direction of the view in which the `rect` is defined. /// - returns: A rect with the receiver's aspect ratio, strictly containing the bounding rect. - public func rect( + @MainActor public func rect( toFill rect: CGRect, at position: Position, in scaleFactor: ScaleFactorProviding, @@ -257,7 +257,7 @@ public struct AspectRatio: Comparable, CustomDebugStringConvertible, Sendable { /// - parameter context: The view/window/screen that provides the scale factor and effective layout direction in /// which the rect should be positioned. /// - returns: A rect with the receiver's aspect ratio, strictly containing the bounding rect. - public func rect( + @MainActor public func rect( toFill rect: CGRect, at position: Position, in context: (ScaleFactorProviding & LayoutDirectionProviding) @@ -294,6 +294,7 @@ extension CGRect { // MARK: - Life Cycle + @MainActor fileprivate init( size newSize: CGSize, at position: Position, diff --git a/Paralayout/LayoutDirection.swift b/Paralayout/LayoutDirection.swift index 1636ca1..499faca 100644 --- a/Paralayout/LayoutDirection.swift +++ b/Paralayout/LayoutDirection.swift @@ -19,6 +19,7 @@ import UIKit /// Defines an object that vends its current user interface layout direction. public protocol LayoutDirectionProviding { + @MainActor var effectiveUserInterfaceLayoutDirection: UIUserInterfaceLayoutDirection { get } } diff --git a/Paralayout/PixelRounding.swift b/Paralayout/PixelRounding.swift index 3ac4f17..857a826 100644 --- a/Paralayout/PixelRounding.swift +++ b/Paralayout/PixelRounding.swift @@ -19,6 +19,7 @@ import UIKit /// The ratio of pixels to points, either of a UIScreen, a UIView's screen, or an explicit value. public protocol ScaleFactorProviding { + @MainActor var pixelsPerPoint: CGFloat { get } } @@ -76,7 +77,7 @@ extension CGFloat { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: The adjusted coordinate. - public func flooredToPixel(in scaleFactor: ScaleFactorProviding) -> CGFloat { + @MainActor public func flooredToPixel(in scaleFactor: ScaleFactorProviding) -> CGFloat { return adjustedToPixel(scaleFactor) { floor($0) } } @@ -85,7 +86,7 @@ extension CGFloat { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: The adjusted coordinate. - public func ceiledToPixel(in scaleFactor: ScaleFactorProviding) -> CGFloat { + @MainActor public func ceiledToPixel(in scaleFactor: ScaleFactorProviding) -> CGFloat { return adjustedToPixel(scaleFactor) { ceil($0) } } @@ -94,7 +95,7 @@ extension CGFloat { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: The adjusted coordinate. - public func roundedToPixel(in scaleFactor: ScaleFactorProviding) -> CGFloat { + @MainActor public func roundedToPixel(in scaleFactor: ScaleFactorProviding) -> CGFloat { // Invoke the namespaced Darwin.round() function since round() is ambiguous (it's also a mutating instance // method). return adjustedToPixel(scaleFactor) { Darwin.round($0) } @@ -102,6 +103,7 @@ extension CGFloat { // MARK: - Private Methods + @MainActor private func adjustedToPixel(_ scaleFactor: ScaleFactorProviding, _ adjustment: (CGFloat) -> CGFloat) -> CGFloat { let scale = scaleFactor.pixelsPerPoint return (scale > 0.0) ? (adjustment(self * scale) / scale) : self @@ -117,7 +119,7 @@ extension CGPoint { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: The adjusted coordinate. - public func flooredToPixel(in scaleFactor: ScaleFactorProviding) -> CGPoint { + @MainActor public func flooredToPixel(in scaleFactor: ScaleFactorProviding) -> CGPoint { return CGPoint(x: x.flooredToPixel(in: scaleFactor), y: y.flooredToPixel(in: scaleFactor)) } @@ -127,7 +129,7 @@ extension CGPoint { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: The adjusted coordinate. - public func ceiledToPixel(in scaleFactor: ScaleFactorProviding) -> CGPoint { + @MainActor public func ceiledToPixel(in scaleFactor: ScaleFactorProviding) -> CGPoint { return CGPoint(x: x.ceiledToPixel(in: scaleFactor), y: y.ceiledToPixel(in: scaleFactor)) } @@ -137,7 +139,7 @@ extension CGPoint { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: The adjusted coordinate. - public func roundedToPixel(in scaleFactor: ScaleFactorProviding) -> CGPoint { + @MainActor public func roundedToPixel(in scaleFactor: ScaleFactorProviding) -> CGPoint { return CGPoint(x: x.roundedToPixel(in: scaleFactor), y: y.roundedToPixel(in: scaleFactor)) } @@ -150,7 +152,7 @@ extension CGSize { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: The adjusted coordinate. - public func flooredToPixel(in scaleFactor: ScaleFactorProviding) -> CGSize { + @MainActor public func flooredToPixel(in scaleFactor: ScaleFactorProviding) -> CGSize { return CGSize(width: width.flooredToPixel(in: scaleFactor), height: height.flooredToPixel(in: scaleFactor)) } @@ -159,7 +161,7 @@ extension CGSize { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: The adjusted coordinate. - public func ceiledToPixel(in scaleFactor: ScaleFactorProviding) -> CGSize { + @MainActor public func ceiledToPixel(in scaleFactor: ScaleFactorProviding) -> CGSize { return CGSize(width: width.ceiledToPixel(in: scaleFactor), height: height.ceiledToPixel(in: scaleFactor)) } @@ -168,7 +170,7 @@ extension CGSize { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: The adjusted coordinate. - public func roundedToPixel(in scaleFactor: ScaleFactorProviding) -> CGSize { + @MainActor public func roundedToPixel(in scaleFactor: ScaleFactorProviding) -> CGSize { return CGSize(width: width.roundedToPixel(in: scaleFactor), height: height.roundedToPixel(in: scaleFactor)) } @@ -181,7 +183,7 @@ extension CGRect { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: A new rect with pixel-aligned boundaries, enclosing the original rect. - public func expandedToPixel(in scaleFactor: ScaleFactorProviding) -> CGRect { + @MainActor public func expandedToPixel(in scaleFactor: ScaleFactorProviding) -> CGRect { return CGRect( left: minX.flooredToPixel(in: scaleFactor), top: minY.flooredToPixel(in: scaleFactor), @@ -195,7 +197,7 @@ extension CGRect { /// - parameter scaleFactor: The pixel scale to use, e.g. a UIScreen, UIView, or explicit value (pass `0` to *not* /// snap to pixel). /// - returns: A new rect with pixel-aligned boundaries, enclosed by the original rect. - public func contractedToPixel(in scaleFactor: ScaleFactorProviding) -> CGRect { + @MainActor public func contractedToPixel(in scaleFactor: ScaleFactorProviding) -> CGRect { return CGRect( left: minX.ceiledToPixel(in: scaleFactor), top: minY.ceiledToPixel(in: scaleFactor), diff --git a/Paralayout/UIFont+CapInsets.swift b/Paralayout/UIFont+CapInsets.swift index 211cda8..6663c06 100644 --- a/Paralayout/UIFont+CapInsets.swift +++ b/Paralayout/UIFont+CapInsets.swift @@ -61,7 +61,7 @@ extension UIFont { /// The space above and below the receiver's capHeight and baseline, as displayed in a UILabel. /// - parameter scaleFactor: The UI scale factor for pixel rounding. /// - returns: The insets. - public func labelCapInsets(in scaleFactor: ScaleFactorProviding) -> LabelCapInsets { + @MainActor public func labelCapInsets(in scaleFactor: ScaleFactorProviding) -> LabelCapInsets { // One would expect ceil(ascender) - floor(descender) so that the baseline would land on a pixel boundary, but // sadly no--this is what `UILabel.sizeToFit()` does. let lineHeight = (ascender - descender).ceiledToPixel(in: scaleFactor) diff --git a/Paralayout/UILabel+Alignment.swift b/Paralayout/UILabel+Alignment.swift index 8aed993..3309c72 100644 --- a/Paralayout/UILabel+Alignment.swift +++ b/Paralayout/UILabel+Alignment.swift @@ -55,6 +55,7 @@ public struct TextRectLayoutProxy: Alignable { // MARK: - Alignable + @MainActor public var alignmentContext: AlignmentContext { var alignmentBounds = proxiedLabel.textRect( forBounds: proxiedLabel.bounds, diff --git a/Paralayout/UIView+Alignment.swift b/Paralayout/UIView+Alignment.swift index cf78b31..b219d9d 100644 --- a/Paralayout/UIView+Alignment.swift +++ b/Paralayout/UIView+Alignment.swift @@ -46,7 +46,7 @@ extension Alignable { /// - parameter otherView: The other view for the measurement. /// - parameter otherPosition: The position in the `otherView`'s untransformed frame to use for the measurement. /// - returns: The offset from the receiver's `position` to the `otherView`'s `otherPosition`. - public func untransformedFrameOffset( + @MainActor public func untransformedFrameOffset( from position: Position, to otherView: Alignable, _ otherPosition: Position, @@ -126,7 +126,7 @@ extension Alignable { /// calculated. Defaults to `.automatic`, which will align the views in the most common way based on their /// relationship in the view hierarchy. /// - parameter offset: An additional offset to apply to the alignment, e.g. to leave a space between the two views. - public func align( + @MainActor public func align( _ position: Position, with otherView: Alignable, _ otherPosition: Position, diff --git a/Paralayout/UIView+AlignmentConveniences.swift b/Paralayout/UIView+AlignmentConveniences.swift index 5e1d971..364cea4 100644 --- a/Paralayout/UIView+AlignmentConveniences.swift +++ b/Paralayout/UIView+AlignmentConveniences.swift @@ -30,7 +30,7 @@ extension Alignable { /// relationship in the view hierarchy. /// - parameter horizontalOffset: An additional horizontal offset to apply to the alignment (defaults to 0). /// - parameter verticalOffset: An additional vertical offset to apply to the alignment (defaults to 0). - public func align( + @MainActor public func align( _ position: Position, with otherView: Alignable, _ otherPosition: Position, @@ -55,7 +55,7 @@ extension Alignable { /// - parameter superviewPosition: The position within the view's `superview` to use for alignment. /// - parameter horizontalOffset: An additional horizontal offset to apply to the alignment (defaults to 0). /// - parameter verticalOffset: An additional vertical offset to apply to the alignment (defaults to 0). - public func align( + @MainActor public func align( _ position: Position, withSuperviewPosition superviewPosition: Position, horizontalOffset: CGFloat = 0, @@ -80,7 +80,7 @@ extension Alignable { /// - parameter position: The position within the receiving view to use for alignment. /// - parameter superviewPosition: The position within the view's `superview` to use for alignment. /// - parameter offset: An additional offset to apply to the alignment. - public func align( + @MainActor public func align( _ position: Position, withSuperviewPosition superviewPosition: Position, offset: UIOffset @@ -105,7 +105,7 @@ extension Alignable { /// - parameter superviewPoint: The coordinate within the view's `superview` to use for alignment. /// - parameter horizontalOffset: An additional horizontal offset to apply to the alignment (defaults to 0). /// - parameter verticalOffset: An additional vertical offset to apply to the alignment (defaults to 0). - public func align( + @MainActor public func align( _ position: Position, withSuperviewPoint superviewPoint: CGPoint, horizontalOffset: CGFloat = 0, @@ -138,7 +138,7 @@ extension Alignable { /// - parameter position: The position in both the receiving view and its `superview` to use for alignment. /// - parameter horizontalOffset: An additional horizontal offset to apply to the receiver. Defaults to no offset. /// - parameter verticalOffset: An additional vertical offset to apply to the receiver. Defaults to no offset. - public func align(withSuperview position: Position, horizontalOffset: CGFloat = 0, verticalOffset: CGFloat = 0) { + @MainActor public func align(withSuperview position: Position, horizontalOffset: CGFloat = 0, verticalOffset: CGFloat = 0) { align( position, withSuperviewPosition: position, @@ -153,7 +153,7 @@ extension Alignable { /// /// - parameter position: The position in both the receiving view and its `superview` to use for alignment. /// - parameter offset: An additional offset to apply to the receiver. - public func align(withSuperview position: Position, offset: UIOffset) { + @MainActor public func align(withSuperview position: Position, offset: UIOffset) { align( position, withSuperviewPosition: position, @@ -168,7 +168,7 @@ extension Alignable { /// - parameter position: The position in both the receiving view and its `superview` to use for alignment. /// - parameter inset: An inset (horizontal, vertical, or diagonal based on the position) to apply. An inset on /// `.center` is interpreted as a vertical offset away from the top. - public func align(withSuperview position: Position, inset: CGFloat) { + @MainActor public func align(withSuperview position: Position, inset: CGFloat) { guard let superview = alignmentContext.view.superview else { fatalError("Can't align view without a superview!") } diff --git a/Paralayout/ViewDistributionItem.swift b/Paralayout/ViewDistributionItem.swift index dae79f8..93082f6 100644 --- a/Paralayout/ViewDistributionItem.swift +++ b/Paralayout/ViewDistributionItem.swift @@ -58,7 +58,7 @@ public enum ViewDistributionItem: ViewDistributionSpecifying { /// - returns: An array of `ViewDistributionItem`s suitable for layout and/or measurement, and tallies of all fixed /// and flexible space. If the distribution is invalid (no views, any view not a subview of the superview, or any /// view repeated in the distribution), returns an empty array. - internal static func items( + @MainActor internal static func items( impliedIn distribution: [ViewDistributionSpecifying], axis: ViewDistributionAxis, superview: UIView? @@ -128,7 +128,7 @@ public enum ViewDistributionItem: ViewDistributionSpecifying { /// Returns the length of the DistributionItem (`axis` and `multiplier` are relevant only for `.view` and /// `.flexible` items, respectively). - internal func layoutSize(along axis: ViewDistributionAxis, multiplier: CGFloat = 1) -> CGFloat { + @MainActor internal func layoutSize(along axis: ViewDistributionAxis, multiplier: CGFloat = 1) -> CGFloat { switch self { case .view(let view, let insets): return axis.size(of: view.untransformedFrame) - axis.amount(of: insets) @@ -148,6 +148,7 @@ public enum ViewDistributionItem: ViewDistributionSpecifying { /// A means of getting a `ViewDistributionItem`: either a UIView, or a number as `.fixed` or `.flexible`. public protocol ViewDistributionSpecifying { + @MainActor var distributionItem: ViewDistributionItem { get } } diff --git a/ParalayoutTests/AspectRatioTests.swift b/ParalayoutTests/AspectRatioTests.swift index b3d1614..d5e7695 100644 --- a/ParalayoutTests/AspectRatioTests.swift +++ b/ParalayoutTests/AspectRatioTests.swift @@ -20,6 +20,7 @@ import XCTest final class AspectRatioTests: XCTestCase { + @MainActor func testStatics() { XCTAssert(AspectRatio.square.height(forWidth: 1, in: 0) == 1) XCTAssert(AspectRatio.square.width(forHeight: 1, in: 0) == 1) @@ -32,6 +33,7 @@ final class AspectRatioTests: XCTestCase { XCTAssert(AspectRatio.widescreen.height(forWidth: 16, in: 0) == 9) } + @MainActor func testCreation() { XCTAssert(AspectRatio(width: 1, height: 1) == AspectRatio.square) XCTAssert(AspectRatio(width: 2, height: 2) == AspectRatio.square) @@ -43,6 +45,7 @@ final class AspectRatioTests: XCTestCase { XCTAssert(CGRect(x: 25, y: 50, width: 100, height: 100).aspectRatio == AspectRatio.square) } + @MainActor func testInverted() { XCTAssert(AspectRatio.square.inverted == AspectRatio.square) XCTAssert(AspectRatio(width: 100, height: 50).inverted == AspectRatio(width: 5, height: 10)) @@ -78,6 +81,7 @@ final class AspectRatioTests: XCTestCase { } } + @MainActor func testSizes() { // The core sizing methods round the resulting dimension. XCTAssert(AspectRatio.square.width(forHeight: 10.5, in: 1) == 11) @@ -97,6 +101,7 @@ final class AspectRatioTests: XCTestCase { } } + @MainActor func testRects() { // Use a set of rectangles with varying aspect ratios (square, landscape, portrait; origin offsets; // non-integral). diff --git a/ParalayoutTests/DistributionTests.swift b/ParalayoutTests/DistributionTests.swift index 2b4695a..03645df 100644 --- a/ParalayoutTests/DistributionTests.swift +++ b/ParalayoutTests/DistributionTests.swift @@ -22,6 +22,7 @@ final class DistributionTests: XCTestCase { // MARK: - Tests - Orthogonal Alignment + @MainActor func testOrthogonalAlignmentInHorizontalDistribution() { // The layout direction shouldn't affect the orthogonal alignment in a horizontal distribution. for forcedLayoutDirection in [UISemanticContentAttribute.forceLeftToRight, .forceRightToLeft] { @@ -87,6 +88,7 @@ final class DistributionTests: XCTestCase { } } + @MainActor func testOrthogonalAlignmentInVerticalDistribution_leftToRightLayout() { let container = UIView(frame: .init(x: 0, y: 0, width: 200, height: 1000)) container.semanticContentAttribute = .forceLeftToRight @@ -149,6 +151,7 @@ final class DistributionTests: XCTestCase { } } + @MainActor func testOrthogonalAlignmentInVerticalDistribution_rightToLeftLayout() { let container = UIView(frame: .init(x: 0, y: 0, width: 200, height: 1000)) container.semanticContentAttribute = .forceRightToLeft diff --git a/ParalayoutTests/PixelRoundingTests.swift b/ParalayoutTests/PixelRoundingTests.swift index d1f90e1..7f2e43f 100644 --- a/ParalayoutTests/PixelRoundingTests.swift +++ b/ParalayoutTests/PixelRoundingTests.swift @@ -22,6 +22,7 @@ final class PixelRoundingTests: XCTestCase { // MARK: - Private Types + @MainActor private enum Samples { static let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) static let view = UIView() @@ -29,6 +30,7 @@ final class PixelRoundingTests: XCTestCase { // MARK: - XCTest + @MainActor override func setUp() { super.setUp() @@ -37,6 +39,7 @@ final class PixelRoundingTests: XCTestCase { // MARK: - Tests - Pixel Rounding + @MainActor func testFloatPixelRounding() { XCTAssertEqual(CGFloat(1.75).flooredToPixel(in: 0), 1.75) XCTAssertEqual(CGFloat(1.75).flooredToPixel(in: TestScreen.at1x), 1) @@ -57,6 +60,7 @@ final class PixelRoundingTests: XCTestCase { XCTAssertEqual(CGFloat(-1.75).ceiledToPixel(in: TestScreen.at2x), -1.5) } + @MainActor func testPointPixelRounding() { XCTAssertEqual(CGPoint(x: 0.9, y: -1.1).flooredToPixel(in: 0), CGPoint(x: 0.9, y: -1.1)) XCTAssertEqual(CGPoint(x: 0.9, y: -1.1).flooredToPixel(in: 1), CGPoint(x: 0, y: -2)) @@ -71,6 +75,7 @@ final class PixelRoundingTests: XCTestCase { XCTAssertEqual(CGPoint(x: 0.4, y: -1.4).roundedToPixel(in: 2), CGPoint(x: 0.5, y: -1.5)) } + @MainActor func testSizePixelRounding() { XCTAssertEqual(CGSize(width: 0.9, height: -1.1).flooredToPixel(in: 0), CGSize(width: 0.9, height: -1.1)) XCTAssertEqual(CGSize(width: 0.9, height: -1.1).flooredToPixel(in: 1), CGSize(width: 0, height: -2)) @@ -85,6 +90,7 @@ final class PixelRoundingTests: XCTestCase { XCTAssertEqual(CGSize(width: 0.4, height: -1.4).roundedToPixel(in: 2), CGSize(width: 0.5, height: -1.5)) } + @MainActor func testRectPixelRounding() { XCTAssertEqual( CGRect(left: 10.6, top: 10.4, right: 50.6, bottom: 50.6).expandedToPixel(in: TestScreen.at2x), @@ -107,6 +113,7 @@ final class PixelRoundingTests: XCTestCase { // MARK: - Tests - Scale Factor + @MainActor func testViewScaleFactor() { // A view should inherit the scale factor of its parent screen. for screen in screensToTest() { @@ -121,6 +128,7 @@ final class PixelRoundingTests: XCTestCase { // MARK: - Private Methods + @MainActor private func screensToTest() -> [UIScreen] { if #available(iOS 13, *) { // In iOS 13 and later, there is a bug around setting `UIWindow.screen` that prevents us from testing diff --git a/ParalayoutTests/UIViewFrameTests.swift b/ParalayoutTests/UIViewFrameTests.swift index 5826351..8a88c13 100644 --- a/ParalayoutTests/UIViewFrameTests.swift +++ b/ParalayoutTests/UIViewFrameTests.swift @@ -23,6 +23,7 @@ final class UIViewFrameTests: XCTestCase { // MARK: - Tests + @MainActor func testUntransformedFrameGetter_simpleFrames() { let view = UIView() @@ -36,6 +37,7 @@ final class UIViewFrameTests: XCTestCase { assertUntransformedFrameIsAccurate(for: view) } + @MainActor func testUntransformedFrameSetter_simpleFrames() { let view = UIView() @@ -44,6 +46,7 @@ final class UIViewFrameTests: XCTestCase { XCTAssertEqual(view.untransformedFrame, newValue) } + @MainActor func testUntransformedFrameGetter_nonIdentityTransform() { let view = UIView(frame: CGRect(x: 10, y: 20, width: 30, height: 40)) @@ -54,6 +57,7 @@ final class UIViewFrameTests: XCTestCase { assertUntransformedFrameIsAccurate(for: view) } + @MainActor func testUntransformedFrameSetter_nonIdentityTransform() { let view = UIView(frame: CGRect(x: 10, y: 20, width: 30, height: 40)) @@ -66,6 +70,7 @@ final class UIViewFrameTests: XCTestCase { XCTAssertEqual(view.transform, transform) } + @MainActor func testUntransformedFrameGetter_nonCenterAnchorPoint() { let view = UIView(frame: CGRect(x: 10, y: 20, width: 30, height: 40)) @@ -76,6 +81,7 @@ final class UIViewFrameTests: XCTestCase { assertUntransformedFrameIsAccurate(for: view) } + @MainActor func testUntransformedFrameSetter_nonCenterAnchorPoint() { let view = UIView(frame: CGRect(x: 10, y: 20, width: 30, height: 40)) @@ -88,6 +94,7 @@ final class UIViewFrameTests: XCTestCase { XCTAssertEqual(view.layer.anchorPoint, anchorPoint) } + @MainActor func testUntransformedFrameGetter_nonZeroOriginBounds() { let view = UIView(frame: CGRect(x: 10, y: 20, width: 30, height: 40)) @@ -95,6 +102,7 @@ final class UIViewFrameTests: XCTestCase { assertUntransformedFrameIsAccurate(for: view) } + @MainActor func testUntransformedFrameSetter_nonZeroOriginBounds() { let view = UIView(frame: CGRect(x: 10, y: 20, width: 30, height: 40)) @@ -107,6 +115,7 @@ final class UIViewFrameTests: XCTestCase { XCTAssertEqual(view.bounds.origin, boundsOrigin) } + @MainActor func testUntransformedConvert_siblingViews() throws { let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) @@ -139,6 +148,7 @@ final class UIViewFrameTests: XCTestCase { try assertUntransformedConvertIsAccurate(for: CGPoint(x: 2, y: 3), in: view2, convertedTo: view1) } + @MainActor func testUntransformedConvert_verticalHierarchy() throws { let view1 = UIView(frame: CGRect(x: 1, y: 2, width: 10, height: 10)) @@ -155,6 +165,7 @@ final class UIViewFrameTests: XCTestCase { try assertUntransformedConvertIsAccurate(for: CGPoint(x: -7, y: 8), in: view3, convertedTo: view1) } + @MainActor func testUntransformedConvert_nonZeroBounds() throws { let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) @@ -176,6 +187,7 @@ final class UIViewFrameTests: XCTestCase { try assertUntransformedConvertIsAccurate(for: CGPoint(x: 1, y: 2), in: view2, convertedTo: view3) } + @MainActor func testUntransformedConvert_nonIdentityTransforms() throws { let view1 = UIView(frame: CGRect(x: 1, y: 2, width: 10, height: 10)) view1.transform = .init(rotationAngle: 0.1) @@ -202,6 +214,7 @@ final class UIViewFrameTests: XCTestCase { // MARK: - Private Helper Methods + @MainActor func assertUntransformedFrameIsAccurate(for view: UIView, file: StaticString = #file, line: UInt = #line) { let actualValue = view.untransformedFrame @@ -213,6 +226,7 @@ final class UIViewFrameTests: XCTestCase { view.layer.transform = originalTransform } + @MainActor func assertUntransformedConvertIsAccurate( for point: CGPoint, in sourceView: UIView, diff --git a/ParalayoutTests/UIViewSizingTests.swift b/ParalayoutTests/UIViewSizingTests.swift index 4d8531e..7de01a5 100644 --- a/ParalayoutTests/UIViewSizingTests.swift +++ b/ParalayoutTests/UIViewSizingTests.swift @@ -22,6 +22,7 @@ final class UIViewSizingTests: XCTestCase { // MARK: - Tests - Size That Fits + @MainActor func testSizeThatFitsWithNoConstraints() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) @@ -31,6 +32,7 @@ final class UIViewSizingTests: XCTestCase { ) } + @MainActor func testSizeThatFitsWithMaxWidthConstraints() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) @@ -40,6 +42,7 @@ final class UIViewSizingTests: XCTestCase { ) } + @MainActor func testSizeThatFitsWithMaxHeightConstraints() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) @@ -49,6 +52,7 @@ final class UIViewSizingTests: XCTestCase { ) } + @MainActor func testSizeThatFitsWithMaxSizeConstraints() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) @@ -58,6 +62,7 @@ final class UIViewSizingTests: XCTestCase { ) } + @MainActor func testSizeThatFitsWithMinWidthConstraints() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) @@ -67,6 +72,7 @@ final class UIViewSizingTests: XCTestCase { ) } + @MainActor func testSizeThatFitsWithMinHeightConstraints() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) @@ -76,6 +82,7 @@ final class UIViewSizingTests: XCTestCase { ) } + @MainActor func testSizeThatFitsWithMinSizeConstraints() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) @@ -87,6 +94,7 @@ final class UIViewSizingTests: XCTestCase { // MARK: - Tests - Size To Fit + @MainActor func testSizeToFitWithNoConstraints() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) testView.sizeToFit(.zero) @@ -97,6 +105,7 @@ final class UIViewSizingTests: XCTestCase { ) } + @MainActor func testSizeToFitWithMaxSizeConstraints() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) testView.sizeToFit(.init(width: 100, height: 50), constraints: .maxSize) @@ -107,6 +116,7 @@ final class UIViewSizingTests: XCTestCase { ) } + @MainActor func testSizeToFitWithTransform() { let testView = TestView(sizeThatFits: .init(width: 300, height: 200)) testView.transform = .init(scaleX: 2, y: 2) @@ -117,6 +127,7 @@ final class UIViewSizingTests: XCTestCase { XCTAssertEqual(testView.frame.size, .init(width: 200, height: 100)) } + @MainActor func testSizeToFitWithNegativeWidth() { let testView = TestView(sizeThatFits: .init(width: -50, height: 200)) testView.sizeToFit(.init(width: 100, height: 50)) @@ -124,6 +135,7 @@ final class UIViewSizingTests: XCTestCase { XCTAssertEqual(testView.bounds.size, .init(width: 0, height: 200)) } + @MainActor func testSizeToFitWithNegativeHeight() { let testView = TestView(sizeThatFits: .init(width: 200, height: -50)) testView.sizeToFit(.init(width: 100, height: 50)) diff --git a/ParalayoutTests/ViewArrayBuilderTests.swift b/ParalayoutTests/ViewArrayBuilderTests.swift index 7f1f92d..cc375d0 100644 --- a/ParalayoutTests/ViewArrayBuilderTests.swift +++ b/ParalayoutTests/ViewArrayBuilderTests.swift @@ -22,6 +22,7 @@ final class ViewArrayBuilderTests: XCTestCase { // MARK: - Tests + @MainActor func testSimpleResultBuilder() throws { let view1 = UIView() let view2 = UIView() @@ -37,6 +38,7 @@ final class ViewArrayBuilderTests: XCTestCase { ) } + @MainActor func testIfTrueResultBuilder() throws { let view1 = UIView() let view2 = UIView() @@ -58,6 +60,7 @@ final class ViewArrayBuilderTests: XCTestCase { ) } + @MainActor func testIfFalseResultBuilder() throws { let view1 = UIView() let view2 = UIView() @@ -78,6 +81,7 @@ final class ViewArrayBuilderTests: XCTestCase { ) } + @MainActor func testIfElseFirstBranchResultBuilder() throws { let view1 = UIView() let view2 = UIView() @@ -102,6 +106,7 @@ final class ViewArrayBuilderTests: XCTestCase { ) } + @MainActor func testIfElseSecondBranchResultBuilder() throws { let view1 = UIView() let view2 = UIView() @@ -126,6 +131,7 @@ final class ViewArrayBuilderTests: XCTestCase { ) } + @MainActor func testSwitchCaseResultBuilder() throws { let view1 = UIView() let view2 = UIView() @@ -150,6 +156,7 @@ final class ViewArrayBuilderTests: XCTestCase { ) } + @MainActor func testSwitchDefaultResultBuilder() throws { let view1 = UIView() let view2 = UIView() @@ -173,6 +180,7 @@ final class ViewArrayBuilderTests: XCTestCase { ) } + @MainActor func testForLoopResultBuilder() throws { let views = [UIView(), UIView(), UIView()] XCTAssertEqual( diff --git a/ParalayoutTests/ViewDistributionBuilderTests.swift b/ParalayoutTests/ViewDistributionBuilderTests.swift index ba2288d..7d97c2a 100644 --- a/ParalayoutTests/ViewDistributionBuilderTests.swift +++ b/ParalayoutTests/ViewDistributionBuilderTests.swift @@ -22,6 +22,7 @@ final class ViewDistributionBuilderTests: XCTestCase { // MARK: - Tests + @MainActor func testSimpleResultBuilder() throws { let view = UIView() XCTAssertEqual( @@ -38,6 +39,7 @@ final class ViewDistributionBuilderTests: XCTestCase { ) } + @MainActor func testIfTrueResultBuilder() throws { let view = UIView() let condition = true @@ -57,6 +59,7 @@ final class ViewDistributionBuilderTests: XCTestCase { ) } + @MainActor func testIfFalseResultBuilder() throws { let view = UIView() let condition = false @@ -75,6 +78,7 @@ final class ViewDistributionBuilderTests: XCTestCase { ) } + @MainActor func testIfElseFirstBranchResultBuilder() throws { let view = UIView() view.tag = 1 @@ -99,6 +103,7 @@ final class ViewDistributionBuilderTests: XCTestCase { ) } + @MainActor func testIfElseSecondBranchResultBuilder() throws { let view = UIView() view.tag = 1 @@ -123,6 +128,7 @@ final class ViewDistributionBuilderTests: XCTestCase { ) } + @MainActor func testSwitchCaseResultBuilder() throws { let view = UIView() let value = 1 @@ -145,6 +151,7 @@ final class ViewDistributionBuilderTests: XCTestCase { ) } + @MainActor func testSwitchDefaultResultBuilder() throws { let view = UIView() let value = 2 @@ -166,6 +173,7 @@ final class ViewDistributionBuilderTests: XCTestCase { ) } + @MainActor func testForLoopResultBuilder() throws { XCTAssertEqual( viewDistribution({ From 4688929b8924b6cc456740bb3407d662e1aa10f3 Mon Sep 17 00:00:00 2001 From: John Szumski Date: Wed, 29 May 2024 13:58:32 -0400 Subject: [PATCH 4/7] Apply strict concurrency for Bazel --- BUILD.bazel | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BUILD.bazel b/BUILD.bazel index 776f42e..8a4a898 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -12,6 +12,7 @@ swift_library( name = "Paralayout.lib", srcs = glob(["Paralayout/**/*.swift"]), module_name = "Paralayout", + copts = ["-strict-concurrency=complete"], tags = ["manual"], visibility = ["//visibility:public"], deps = [], @@ -22,6 +23,7 @@ swift_library( testonly = True, srcs = glob(["ParalayoutTests/**/*.swift"]), module_name = "ParalayoutTests", + copts = ["-strict-concurrency=complete"], tags = ["manual"], deps = [":Paralayout.lib"], ) From 0f0ae1ca226d03c92bb5b09d297f1cbc1ee86e62 Mon Sep 17 00:00:00 2001 From: John Szumski <784312+jszumski@users.noreply.github.com> Date: Wed, 29 May 2024 15:17:36 -0400 Subject: [PATCH 5/7] Update Bazel workflow to use Xcode 15.4 --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0051490..28c0eb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,9 +63,11 @@ jobs: run: Scripts/build.swift spm ${{ matrix.platform }} `which xcpretty` bazel: name: Bazel - runs-on: macOS-12 + runs-on: macOS-latest steps: - name: Checkout Repo uses: actions/checkout@v4 + - name: Select Xcode Version + run: sudo xcode-select --switch /Applications/Xcode_15.4.app/Contents/Developer - name: Build and Test - run: bazel test //... + run: bazel test //... --xcode_version=15.4.0 From a304b614a9fecf41c3a7489cb8c7b2e5897c4822 Mon Sep 17 00:00:00 2001 From: John Szumski Date: Wed, 29 May 2024 15:29:26 -0400 Subject: [PATCH 6/7] More call site adjustment in tests --- .../ViewDistributionSnapshotTests.swift | 4 ++++ .../ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift | 2 ++ ParalayoutTests/DistributionTests.swift | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Example/ParalayoutSnapshotTests/ViewDistributionSnapshotTests.swift b/Example/ParalayoutSnapshotTests/ViewDistributionSnapshotTests.swift index 7a63b0e..fd2f9f7 100644 --- a/Example/ParalayoutSnapshotTests/ViewDistributionSnapshotTests.swift +++ b/Example/ParalayoutSnapshotTests/ViewDistributionSnapshotTests.swift @@ -19,6 +19,7 @@ import SnapshotTesting final class ViewDistributionSnapshotTests: SnapshotTestCase { + @MainActor func testDistribution() { let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 200)) containerView.backgroundColor = .white @@ -45,6 +46,7 @@ final class ViewDistributionSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: ["vertical"])) } + @MainActor func testDistributionIgnoresTransform() { let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 200)) containerView.backgroundColor = .white @@ -72,6 +74,7 @@ final class ViewDistributionSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: [])) } + @MainActor func testDistributionUsingCapInsets() { let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 80)) containerView.backgroundColor = .white @@ -104,6 +107,7 @@ final class ViewDistributionSnapshotTests: SnapshotTestCase { assertSnapshot(matching: containerView, as: .image, named: nameForSnapshot(with: [])) } + @MainActor func testHorizontalDistributionFollowsLayoutDirection() { let view = HorizontalDistributionView(frame: CGRect(x: 0, y: 0, width: 160, height: 60)) diff --git a/Example/ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift b/Example/ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift index 6c03698..b86c8c8 100644 --- a/Example/ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift +++ b/Example/ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift @@ -19,6 +19,7 @@ import SnapshotTesting final class ViewSpeadingSnapshotTests: SnapshotTestCase { + @MainActor func testHorizontallySpreadSubviews() { let container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 100)) container.backgroundColor = .white @@ -92,6 +93,7 @@ final class ViewSpeadingSnapshotTests: SnapshotTestCase { verifySnapshot(margin: 40, inRect: CGRect(x: 20, y: 10, width: 300, height: 50)) } + @MainActor func testVerticallySpreadSubviews() { let container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 100)) container.backgroundColor = .white diff --git a/ParalayoutTests/DistributionTests.swift b/ParalayoutTests/DistributionTests.swift index 03645df..2e04a14 100644 --- a/ParalayoutTests/DistributionTests.swift +++ b/ParalayoutTests/DistributionTests.swift @@ -34,6 +34,7 @@ final class DistributionTests: XCTestCase { let secondSubview = UIView(frame: .init(x: 0, y: 0, width: 50, height: 100)) container.addSubview(secondSubview) + @MainActor func test( alignment: VerticalDistributionAlignment?, inRect layoutRect: CGRect? = nil, @@ -98,6 +99,7 @@ final class DistributionTests: XCTestCase { let secondSubview = UIView(frame: .init(x: 0, y: 0, width: 100, height: 50)) container.addSubview(secondSubview) + @MainActor func test( alignment: HorizontalDistributionAlignment?, inRect layoutRect: CGRect? = nil, @@ -161,6 +163,7 @@ final class DistributionTests: XCTestCase { let secondSubview = UIView(frame: .init(x: 0, y: 0, width: 100, height: 50)) container.addSubview(secondSubview) + @MainActor func test( alignment: HorizontalDistributionAlignment?, inRect layoutRect: CGRect? = nil, From 874d7e0b76a80aa4af9f8b64c7816c24878b5473 Mon Sep 17 00:00:00 2001 From: John Szumski Date: Wed, 29 May 2024 15:43:42 -0400 Subject: [PATCH 7/7] More call site adjustment in tests --- .../ParalayoutSnapshotTests/ViewAlignmentSnapshotTests.swift | 2 ++ .../ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Example/ParalayoutSnapshotTests/ViewAlignmentSnapshotTests.swift b/Example/ParalayoutSnapshotTests/ViewAlignmentSnapshotTests.swift index a25c951..57883f4 100644 --- a/Example/ParalayoutSnapshotTests/ViewAlignmentSnapshotTests.swift +++ b/Example/ParalayoutSnapshotTests/ViewAlignmentSnapshotTests.swift @@ -91,6 +91,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { targetView.backgroundColor = .lightGray containerView.addSubview(targetView) + @MainActor func addAlignedSubview( receiverPosition: Position, receiverLayoutDirection: UIUserInterfaceLayoutDirection, @@ -402,6 +403,7 @@ final class ViewAlignmentSnapshotTests: SnapshotTestCase { receiverView.transform = receiverTransform containerView.addSubview(receiverView) + @MainActor func updateMirrorViews() { targetView.transform = .identity targetFrameView.frame = targetView.frame.applying(targetView.transform.inverted()) diff --git a/Example/ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift b/Example/ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift index b86c8c8..7005321 100644 --- a/Example/ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift +++ b/Example/ParalayoutSnapshotTests/ViewSpreadingSnapshotTests.swift @@ -36,6 +36,7 @@ final class ViewSpeadingSnapshotTests: SnapshotTestCase { greenView.backgroundColor = .green container.addSubview(greenView) + @MainActor func verifySnapshot( margin: CGFloat = 0, inRect rect: CGRect? = nil, @@ -110,6 +111,7 @@ final class ViewSpeadingSnapshotTests: SnapshotTestCase { greenView.backgroundColor = .green container.addSubview(greenView) + @MainActor func verifySnapshot( margin: CGFloat = 0, inRect rect: CGRect? = nil,