Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ concurrency:
cancel-in-progress: true

jobs:
swift-6-build:
runs-on: macos-26
timeout-minutes: 15

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Select Xcode
uses: mxcl/xcodebuild@v3
with:
xcode: '26.6'
action: none

- name: Build library in Swift 6 mode
run: |
swift build \
--target AsyncImageView \
--triple arm64-apple-ios18.0-simulator \
--sdk "$(xcrun --sdk iphonesimulator --show-sdk-path)"

test:
runs-on: macos-26
timeout-minutes: 30
Expand Down
2 changes: 1 addition & 1 deletion AsyncImageView/AsyncImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import UIKit
import Combine

import ReactiveSwift
@preconcurrency import ReactiveSwift

public protocol ImageViewDataType {
associatedtype RenderData: RenderDataType
Expand Down
2 changes: 1 addition & 1 deletion AsyncImageView/Renderers/ImageInflaterRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public final class ImageInflaterRenderer<Renderer: RendererType>: RendererType {
}
}

public enum ImageInflaterRendererContentMode {
public enum ImageInflaterRendererContentMode: Sendable {
case aspectFill
case aspectFit

Expand Down
2 changes: 1 addition & 1 deletion AsyncImageView/Renderers/Renderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import UIKit
import ReactiveSwift

/// Information required to produce an image
public protocol RenderDataType: Hashable {
public protocol RenderDataType: Hashable, Sendable {
var size: CGSize { get }
}

Expand Down
43 changes: 25 additions & 18 deletions AsyncImageView/Renderers/ViewRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
import UIKit
import CoreGraphics

import ReactiveSwift
@preconcurrency import ReactiveSwift

#if !os(watchOS)

/// `RendererType` which generates a `UIImage` from a UIView.
@available(iOS 10.0, tvOSApplicationExtension 10.0, *)
public final class ViewRenderer<Data: RenderDataType>: RendererType {
public typealias Block = (_ data: Data) -> UIView
@MainActor
public final class ViewRenderer<Data: RenderDataType>: @MainActor RendererType {
public typealias Block = @MainActor (_ data: Data) -> UIView

private let format: UIGraphicsImageRendererFormat
private let viewCreationBlock: Block
Expand Down Expand Up @@ -49,8 +50,9 @@ public final class ViewRenderer<Data: RenderDataType>: RendererType {
}

/// `RendererType` which generates a `UIImage` from a UIView.
public final class OldViewRenderer<Data: RenderDataType>: RendererType {
public typealias Block = (_ data: Data) -> UIView
@MainActor
public final class OldViewRenderer<Data: RenderDataType>: @MainActor RendererType {
public typealias Block = @MainActor (_ data: Data) -> UIView

private let opaque: Bool
private let viewCreationBlock: Block
Expand Down Expand Up @@ -82,27 +84,32 @@ public final class OldViewRenderer<Data: RenderDataType>: RendererType {

fileprivate func createProducer<Data: RenderDataType>(
_ data: Data,
viewCreationBlock: @escaping (_ data: Data) -> UIView,
renderBlock: @escaping (UIView) -> UIImage
viewCreationBlock: @escaping @MainActor (_ data: Data) -> UIView,
renderBlock: @escaping @MainActor (UIView) -> UIImage
) -> SignalProducer<UIImage, Never> {
return SignalProducer { observer, lifetime in
let view = viewCreationBlock(data)
view.frame.origin = .zero
view.bounds.size = data.size
view.layoutIfNeeded()

// Make the CA renderer wait "until all the post-commit triggers fire".
// We can't take a snapshot right away because the view has not been commited to the render server yet.
DispatchQueue.main.async {
if !lifetime.hasEnded {
observer.send(value: renderBlock(view))
observer.sendCompleted()
MainActor.assumeIsolated {
let view = viewCreationBlock(data)
view.frame.origin = .zero
view.bounds.size = data.size
view.layoutIfNeeded()

// Make the CA renderer wait "until all the post-commit triggers fire".
// We can't take a snapshot right away because the view has not been commited to the render server yet.
UIScheduler().schedule {
MainActor.assumeIsolated {
if !lifetime.hasEnded {
observer.send(value: renderBlock(view))
observer.sendCompleted()
}
}
}
}
}
.start(on: UIScheduler())
}

@MainActor
fileprivate func draw(view: UIView, inContext context: CGContext) {
view.layer.render(in: context)
}
Expand Down
2 changes: 1 addition & 1 deletion AsyncImageView/SynchronousUIScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal final class SynchronousUIScheduler: Scheduler {
if Thread.isMainThread {
action()
} else {
DispatchQueue.main.async {
UIScheduler().schedule {
if !disposable.isDisposed {
action()
}
Expand Down
14 changes: 11 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,23 @@ let package = Package(
.target(
name: "AsyncImageView",
dependencies: ["ReactiveSwift"],
path: "AsyncImageView"
path: "AsyncImageView",
swiftSettings: [
// Work around https://github.com/swiftlang/swift/issues/75453.
.unsafeFlags(["-disable-dynamic-actor-isolation"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep unsafe flags out of the published library target

When AsyncImageView is consumed through a versioned SwiftPM dependency rather than built as the root package, SwiftPM rejects the product with the target 'AsyncImageView' in product 'AsyncImageView' contains unsafe build flags. The new CI job builds the repository directly, so it does not exercise this downstream-package restriction. Pass this compiler workaround from the CI build invocation instead of attaching it to the library target.

Useful? React with 👍 / 👎.

]
),
.testTarget(
name: "AsyncImageViewTests",
dependencies: [
"AsyncImageView"
],
path: "AsyncImageViewTests"
path: "AsyncImageViewTests",
swiftSettings: [
// Work around https://github.com/swiftlang/swift/issues/75453.
.unsafeFlags(["-disable-dynamic-actor-isolation"])
Comment thread
NachoSoto marked this conversation as resolved.
]
)
],
swiftLanguageVersions: [.v5]
swiftLanguageModes: [.v6]
)
Loading