diff --git a/Examples/Demo/Demo.xcodeproj/project.pbxproj b/Examples/Demo/Demo.xcodeproj/project.pbxproj index 557d8250..428242d2 100644 --- a/Examples/Demo/Demo.xcodeproj/project.pbxproj +++ b/Examples/Demo/Demo.xcodeproj/project.pbxproj @@ -288,7 +288,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -342,7 +342,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -360,6 +360,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; + DEVELOPMENT_TEAM = 6Q6JDZK895; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -367,7 +368,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -389,6 +390,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; + DEVELOPMENT_TEAM = 6Q6JDZK895; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -396,7 +398,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Package.resolved b/Package.resolved index 1874936d..4ec18605 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,12 +1,13 @@ { + "originHash" : "775133063413f538b6f898bf60cb1bb8a6a0a7c228bf6cf014cef522a89c2168", "pins" : [ { "identity" : "networkimage", "kind" : "remoteSourceControl", "location" : "https://github.com/gonzalezreal/NetworkImage", "state" : { - "revision" : "7aff8d1b31148d32c5933d75557d42f6323ee3d1", - "version" : "6.0.0" + "revision" : "2849f5323265386e200484b0d0f896e73c3411b9", + "version" : "6.0.1" } }, { @@ -18,15 +19,42 @@ "version" : "0.5.0" } }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", + "version" : "1.3.3" + } + }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "26ed3a2b4a2df47917ca9b790a57f91285b923fb", - "version" : "1.12.0" + "revision" : "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b", + "version" : "1.18.7" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "4799286537280063c85a32f09884cfbca301b1a1", + "version" : "602.0.0" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "4c27acf5394b645b70d8ba19dc249c0472d5f618", + "version" : "1.7.0" } } ], - "version" : 2 + "version" : 3 } diff --git a/Package.swift b/Package.swift index 5fb61bc9..9217946f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,15 +1,16 @@ -// swift-tools-version:5.6 +// swift-tools-version:6.2 import PackageDescription let package = Package( name: "swift-markdown-ui", platforms: [ - .macOS(.v12), - .iOS(.v15), - .tvOS(.v15), - .macCatalyst(.v15), - .watchOS(.v8), + .macOS(.v26), + .iOS(.v26), + .tvOS(.v26), + .macCatalyst(.v26), + .watchOS(.v26), + .visionOS(.v26), ], products: [ .library( @@ -18,9 +19,9 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/gonzalezreal/NetworkImage", from: "6.0.0"), - .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.10.0"), - .package(url: "https://github.com/swiftlang/swift-cmark", from: "0.4.0"), + .package(url: "https://github.com/gonzalezreal/NetworkImage", from: "6.0.1"), + .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.7"), + .package(url: "https://github.com/swiftlang/swift-cmark", from: "0.5.0"), ], targets: [ .target( diff --git a/README.md b/README.md index 486e9fee..e6e67d2f 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,13 @@ You can use the built-in themes, create your own or override specific text and b You can use MarkdownUI on the following platforms: -- macOS 12.0+ -- iOS 15.0+ -- tvOS 15.0+ -- watchOS 8.0+ +- macOS 26.0+ +- iOS 26.0+ +- tvOS 26.0+ +- watchOS 26.0+ +- visionOS 26.0+ -Some features, like displaying tables or multi-image paragraphs, require macOS 13.0+, iOS 16.0+, -tvOS 16.0+, and watchOS 9.0+. +This library has been updated for Swift 6.2 and the latest Apple platforms (2025). ## Getting started diff --git a/Sources/MarkdownUI/DSL/Inlines/SoftBreak.swift b/Sources/MarkdownUI/DSL/Inlines/SoftBreak.swift index 95551353..ad49dcd4 100644 --- a/Sources/MarkdownUI/DSL/Inlines/SoftBreak.swift +++ b/Sources/MarkdownUI/DSL/Inlines/SoftBreak.swift @@ -13,7 +13,7 @@ public struct SoftBreak: InlineContentProtocol { } extension SoftBreak { - public enum Mode { + public enum Mode: Sendable { /// Treat a soft break as a space case space diff --git a/Sources/MarkdownUI/Extensibility/AssetImageProvider.swift b/Sources/MarkdownUI/Extensibility/AssetImageProvider.swift index abeb0030..e02205aa 100644 --- a/Sources/MarkdownUI/Extensibility/AssetImageProvider.swift +++ b/Sources/MarkdownUI/Extensibility/AssetImageProvider.swift @@ -12,16 +12,16 @@ import SwiftUI /// } /// .markdownImageProvider(.asset) /// ``` -public struct AssetImageProvider: ImageProvider { - private let name: (URL) -> String +public struct AssetImageProvider: ImageProvider, Sendable { + private let name: @Sendable (URL) -> String private let bundle: Bundle? /// Creates an asset image provider. /// - Parameters: /// - name: A closure that extracts the image resource name from the URL in the Markdown content. - /// - bundle: The bundle where the image resources are located. Specify `nil` to search the app’s main bundle. + /// - bundle: The bundle where the image resources are located. Specify `nil` to search the app's main bundle. public init( - name: @escaping (URL) -> String = \.lastPathComponent, + name: @escaping @Sendable (URL) -> String = \.lastPathComponent, bundle: Bundle? = nil ) { self.name = name diff --git a/Sources/MarkdownUI/Extensibility/AssetInlineImageProvider.swift b/Sources/MarkdownUI/Extensibility/AssetInlineImageProvider.swift index c88f2fe7..c78ec558 100644 --- a/Sources/MarkdownUI/Extensibility/AssetInlineImageProvider.swift +++ b/Sources/MarkdownUI/Extensibility/AssetInlineImageProvider.swift @@ -2,15 +2,15 @@ import SwiftUI /// An inline image provider that loads images from resources located in an app or a module. public struct AssetInlineImageProvider: InlineImageProvider { - private let name: (URL) -> String + private let name: @Sendable (URL) -> String private let bundle: Bundle? /// Creates an asset inline image provider. /// - Parameters: /// - name: A closure that extracts the image resource name from the URL in the Markdown content. - /// - bundle: The bundle where the image resources are located. Specify `nil` to search the app’s main bundle. + /// - bundle: The bundle where the image resources are located. Specify `nil` to search the app's main bundle. public init( - name: @escaping (URL) -> String = \.lastPathComponent, + name: @escaping @Sendable (URL) -> String = \.lastPathComponent, bundle: Bundle? = nil ) { self.name = name diff --git a/Sources/MarkdownUI/Extensibility/CodeSyntaxHighlighter.swift b/Sources/MarkdownUI/Extensibility/CodeSyntaxHighlighter.swift index b725dd40..2f431ecf 100644 --- a/Sources/MarkdownUI/Extensibility/CodeSyntaxHighlighter.swift +++ b/Sources/MarkdownUI/Extensibility/CodeSyntaxHighlighter.swift @@ -4,7 +4,7 @@ import SwiftUI /// /// To configure the current code syntax highlighter for a view hierarchy, use the /// `markdownCodeSyntaxHighlighter(_:)` modifier. -public protocol CodeSyntaxHighlighter { +public protocol CodeSyntaxHighlighter: Sendable { /// Returns a text view configured with the syntax highlighted code. /// - Parameters: /// - code: The code block. diff --git a/Sources/MarkdownUI/Extensibility/DefaultImageProvider.swift b/Sources/MarkdownUI/Extensibility/DefaultImageProvider.swift index cce0c086..dee216ee 100644 --- a/Sources/MarkdownUI/Extensibility/DefaultImageProvider.swift +++ b/Sources/MarkdownUI/Extensibility/DefaultImageProvider.swift @@ -2,7 +2,7 @@ import NetworkImage import SwiftUI /// The default image provider, which loads images from the network. -public struct DefaultImageProvider: ImageProvider { +public struct DefaultImageProvider: ImageProvider, Sendable { public func makeImage(url: URL?) -> some View { NetworkImage(url: url) { state in switch state { diff --git a/Sources/MarkdownUI/Extensibility/ImageProvider.swift b/Sources/MarkdownUI/Extensibility/ImageProvider.swift index 937319b2..32c6937b 100644 --- a/Sources/MarkdownUI/Extensibility/ImageProvider.swift +++ b/Sources/MarkdownUI/Extensibility/ImageProvider.swift @@ -26,12 +26,12 @@ public protocol ImageProvider { @ViewBuilder func makeImage(url: URL?) -> Body } -struct AnyImageProvider: ImageProvider { - private let _makeImage: (URL?) -> AnyView +struct AnyImageProvider: ImageProvider, @unchecked Sendable { + private let _makeImage: @Sendable (URL?) -> AnyView - init(_ imageProvider: I) { - self._makeImage = { - AnyView(imageProvider.makeImage(url: $0)) + init(_ imageProvider: I) where I: Sendable { + self._makeImage = { url in + AnyView(imageProvider.makeImage(url: url)) } } diff --git a/Sources/MarkdownUI/Extensibility/InlineImageProvider.swift b/Sources/MarkdownUI/Extensibility/InlineImageProvider.swift index 06bd9e1c..b4c9c32e 100644 --- a/Sources/MarkdownUI/Extensibility/InlineImageProvider.swift +++ b/Sources/MarkdownUI/Extensibility/InlineImageProvider.swift @@ -4,7 +4,7 @@ import SwiftUI /// /// To configure the current inline image provider for a view hierarchy, /// use the `markdownInlineImageProvider(_:)` modifier. -public protocol InlineImageProvider { +public protocol InlineImageProvider: Sendable { /// Returns an image for the given URL. /// /// ``Markdown`` views call this method to load images within a line of text. diff --git a/Sources/MarkdownUI/Theme/BlockStyle/BlockConfiguration.swift b/Sources/MarkdownUI/Theme/BlockStyle/BlockConfiguration.swift index 0ee2a56d..f032337d 100644 --- a/Sources/MarkdownUI/Theme/BlockStyle/BlockConfiguration.swift +++ b/Sources/MarkdownUI/Theme/BlockStyle/BlockConfiguration.swift @@ -8,7 +8,7 @@ import SwiftUI public struct BlockConfiguration { /// A type-erased view of a Markdown block. public struct Label: View { - init(_ label: L) { + public init(_ label: L) { self.body = AnyView(label) } diff --git a/Sources/MarkdownUI/Theme/BlockStyle/BlockStyle.swift b/Sources/MarkdownUI/Theme/BlockStyle/BlockStyle.swift index 5dee5ca7..05fda364 100644 --- a/Sources/MarkdownUI/Theme/BlockStyle/BlockStyle.swift +++ b/Sources/MarkdownUI/Theme/BlockStyle/BlockStyle.swift @@ -37,16 +37,16 @@ import SwiftUI /// ``` /// /// ![](CustomBlockquote) -public struct BlockStyle { - private let body: (Configuration) -> AnyView +public struct BlockStyle: @unchecked Sendable { + private let body: @Sendable @MainActor (Configuration) -> AnyView /// Creates a block style that customizes a block by applying the given body. /// - Parameter body: A view builder that returns the customized block. - public init(@ViewBuilder body: @escaping (_ configuration: Configuration) -> Body) { - self.body = { AnyView(body($0)) } + public init(@ViewBuilder body: @escaping @Sendable @MainActor (_ configuration: Configuration) -> Body) { + self.body = { @Sendable @MainActor in AnyView(body($0)) } } - func makeBody(configuration: Configuration) -> AnyView { + @MainActor func makeBody(configuration: Configuration) -> AnyView { self.body(configuration) } } @@ -54,8 +54,8 @@ public struct BlockStyle { extension BlockStyle where Configuration == Void { /// Creates a block style for a block with no content, like a thematic break. /// - Parameter body: A view builder that returns the customized block. - public init(@ViewBuilder body: @escaping () -> Body) { - self.init { _ in + public init(@ViewBuilder body: @escaping @Sendable @MainActor () -> Body) { + self.init { @Sendable @MainActor _ in body() } } diff --git a/Sources/MarkdownUI/Theme/BlockStyle/TableBackgroundStyle.swift b/Sources/MarkdownUI/Theme/BlockStyle/TableBackgroundStyle.swift index 85e86af9..a53a1fc3 100644 --- a/Sources/MarkdownUI/Theme/BlockStyle/TableBackgroundStyle.swift +++ b/Sources/MarkdownUI/Theme/BlockStyle/TableBackgroundStyle.swift @@ -27,13 +27,13 @@ import SwiftUI /// ``` /// /// ![](CustomTableBackground) -public struct TableBackgroundStyle { - let background: (_ row: Int, _ column: Int) -> AnyShapeStyle +public struct TableBackgroundStyle: Sendable { + let background: @Sendable (_ row: Int, _ column: Int) -> AnyShapeStyle /// Creates a table background style that customizes table backgrounds by applying a given closure /// to the background of each cell. /// - Parameter background: A closure that returns a shape style for a given table cell location. - public init(background: @escaping (_ row: Int, _ column: Int) -> S) { + public init(background: @escaping @Sendable (_ row: Int, _ column: Int) -> S) { self.background = { row, column in AnyShapeStyle(background(row, column)) } diff --git a/Sources/MarkdownUI/Theme/BlockStyle/TableBorderStyle.swift b/Sources/MarkdownUI/Theme/BlockStyle/TableBorderStyle.swift index 06a6921a..a6dfe232 100644 --- a/Sources/MarkdownUI/Theme/BlockStyle/TableBorderStyle.swift +++ b/Sources/MarkdownUI/Theme/BlockStyle/TableBorderStyle.swift @@ -30,7 +30,7 @@ import SwiftUI /// ``` /// /// ![](CustomTableBorders) -public struct TableBorderStyle { +public struct TableBorderStyle: Sendable { /// The visible table borders. public var visibleBorders: TableBorderSelector diff --git a/Sources/MarkdownUI/Theme/TextStyle/Styles/FontProperties.swift b/Sources/MarkdownUI/Theme/TextStyle/Styles/FontProperties.swift index 10090cd8..e8950d33 100644 --- a/Sources/MarkdownUI/Theme/TextStyle/Styles/FontProperties.swift +++ b/Sources/MarkdownUI/Theme/TextStyle/Styles/FontProperties.swift @@ -1,9 +1,9 @@ import SwiftUI /// The characteristics of a font. -public struct FontProperties: Hashable { +public struct FontProperties: Hashable, Sendable { /// The font family. - public enum Family: Hashable { + public enum Family: Hashable, Sendable { /// The system font family. case system(Font.Design = .default) @@ -12,7 +12,7 @@ public struct FontProperties: Hashable { } /// The font family variant. - public enum FamilyVariant: Hashable { + public enum FamilyVariant: Hashable, Sendable { /// No variant. Use the current font family. case normal @@ -21,7 +21,7 @@ public struct FontProperties: Hashable { } /// The font caps variant. - public enum CapsVariant: Hashable { + public enum CapsVariant: Hashable, Sendable { /// Don't use a font caps variant. case normal @@ -36,7 +36,7 @@ public struct FontProperties: Hashable { } /// The font digit variant. - public enum DigitVariant: Hashable { + public enum DigitVariant: Hashable, Sendable { /// Don't use a font digit variant. case normal @@ -45,7 +45,7 @@ public struct FontProperties: Hashable { } /// The font style. - public enum Style { + public enum Style: Sendable { /// Don't use a font style. case normal @@ -98,11 +98,24 @@ public struct FontProperties: Hashable { /// The font width. @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) public var width: Font.Width { - get { (self.widthStorage as? Font.Width) ?? .standard } - set { self.widthStorage = newValue } + get { + if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) { + return (self.widthStorage?.value as? Font.Width) ?? .standard + } + return .standard + } + set { + if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) { + self.widthStorage = SendableHashable(value: newValue) + } + } } - private var widthStorage: AnyHashable? + private var widthStorage: SendableHashable? + + private struct SendableHashable: Hashable, @unchecked Sendable { + let value: AnyHashable + } /// The font size. public var size: CGFloat = Self.defaultSize diff --git a/Sources/MarkdownUI/Theme/TextStyle/TextStyle.swift b/Sources/MarkdownUI/Theme/TextStyle/TextStyle.swift index 29316dcb..77a7940c 100644 --- a/Sources/MarkdownUI/Theme/TextStyle/TextStyle.swift +++ b/Sources/MarkdownUI/Theme/TextStyle/TextStyle.swift @@ -57,6 +57,6 @@ import SwiftUI /// ``` /// /// ![](CustomBlockquote) -public protocol TextStyle { +public protocol TextStyle: Sendable { func _collectAttributes(in attributes: inout AttributeContainer) } diff --git a/Sources/MarkdownUI/Theme/Theme+Basic.swift b/Sources/MarkdownUI/Theme/Theme+Basic.swift index b5567b59..023c1002 100644 --- a/Sources/MarkdownUI/Theme/Theme+Basic.swift +++ b/Sources/MarkdownUI/Theme/Theme+Basic.swift @@ -14,7 +14,7 @@ extension Theme { /// Bulleted list | ![](NestedBulletedList) /// Numbered list | ![](NumberedList) /// Table | ![](Table-Collection) - public static let basic = Theme() + @MainActor public static let basic = Theme() .code { FontFamilyVariant(.monospaced) FontSize(.em(0.94)) diff --git a/Sources/MarkdownUI/Theme/Theme+DocC.swift b/Sources/MarkdownUI/Theme/Theme+DocC.swift index 43cf1939..b3010965 100644 --- a/Sources/MarkdownUI/Theme/Theme+DocC.swift +++ b/Sources/MarkdownUI/Theme/Theme+DocC.swift @@ -14,7 +14,7 @@ extension Theme { /// Bulleted list | ![](DocCNestedBulletedList) /// Numbered list | ![](DocCNumberedList) /// Table | ![](DocCTable) - public static let docC = Theme() + @MainActor public static let docC = Theme() .text { ForegroundColor(.text) } diff --git a/Sources/MarkdownUI/Theme/Theme+GitHub.swift b/Sources/MarkdownUI/Theme/Theme+GitHub.swift index 90f32ff3..fe1cc040 100644 --- a/Sources/MarkdownUI/Theme/Theme+GitHub.swift +++ b/Sources/MarkdownUI/Theme/Theme+GitHub.swift @@ -14,7 +14,7 @@ extension Theme { /// Bulleted list | ![](GitHubNestedBulletedList) /// Numbered list | ![](GitHubNumberedList) /// Table | ![](GitHubTable) - public static let gitHub = Theme() + @MainActor public static let gitHub = Theme() .text { ForegroundColor(.text) BackgroundColor(.background) diff --git a/Sources/MarkdownUI/Theme/Theme.swift b/Sources/MarkdownUI/Theme/Theme.swift index b6d9ca9b..dba19453 100644 --- a/Sources/MarkdownUI/Theme/Theme.swift +++ b/Sources/MarkdownUI/Theme/Theme.swift @@ -253,7 +253,7 @@ extension Theme { /// Adds a level 1 heading style to the theme. /// - Parameter body: A view builder that returns a customized level 1 heading. public func heading1( - @ViewBuilder body: @escaping (_ configuration: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ configuration: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.heading1 = .init(body: body) @@ -263,7 +263,7 @@ extension Theme { /// Adds a level 2 heading style to the theme. /// - Parameter body: A view builder that returns a customized level 2 heading. public func heading2( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.heading2 = .init(body: body) @@ -273,7 +273,7 @@ extension Theme { /// Adds a level 3 heading style to the theme. /// - Parameter body: A view builder that returns a customized level 3 heading. public func heading3( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.heading3 = .init(body: body) @@ -283,7 +283,7 @@ extension Theme { /// Adds a level 4 heading style to the theme. /// - Parameter body: A view builder that returns a customized level 4 heading. public func heading4( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.heading4 = .init(body: body) @@ -293,7 +293,7 @@ extension Theme { /// Adds a level 5 heading style to the theme. /// - Parameter body: A view builder that returns a customized level 5 heading. public func heading5( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.heading5 = .init(body: body) @@ -303,7 +303,7 @@ extension Theme { /// Adds a level 6 heading style to the theme. /// - Parameter body: A view builder that returns a customized level 6 heading. public func heading6( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.heading6 = .init(body: body) @@ -313,7 +313,7 @@ extension Theme { /// Adds a paragraph style to the theme. /// - Parameter body: A view builder that returns a customized paragraph. public func paragraph( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.paragraph = .init(body: body) @@ -323,7 +323,7 @@ extension Theme { /// Adds a blockquote style to the theme. /// - Parameter body: A view builder that returns a customized blockquote. public func blockquote( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.blockquote = .init(body: body) @@ -333,7 +333,7 @@ extension Theme { /// Adds a code block style to the theme. /// - Parameter body: A view builder that returns a customized code block. public func codeBlock( - @ViewBuilder body: @escaping (_ configuration: CodeBlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ configuration: CodeBlockConfiguration) -> Body ) -> Theme { var theme = self theme.codeBlock = .init(body: body) @@ -343,7 +343,7 @@ extension Theme { /// Adds an image style to the theme. /// - Parameter body: A view builder that returns a customized image. public func image( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.image = .init(body: body) @@ -353,7 +353,7 @@ extension Theme { /// Adds a list style to the theme. /// - Parameter body: A view builder that returns a customized list. public func list( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.list = .init(body: body) @@ -363,7 +363,7 @@ extension Theme { /// Adds a list item style to the theme. /// - Parameter body: A view builder that returns a customized list item. public func listItem( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.listItem = .init(body: body) @@ -381,7 +381,7 @@ extension Theme { /// Adds a task list marker style to the theme. /// - Parameter body: A view builder that returns a customized task list marker. public func taskListMarker( - @ViewBuilder body: @escaping (_ configuration: TaskListMarkerConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ configuration: TaskListMarkerConfiguration) -> Body ) -> Theme { var theme = self theme.taskListMarker = .init(body: body) @@ -401,7 +401,7 @@ extension Theme { /// Adds a bulleted list marker style to the theme. /// - Parameter body: A view builder that returns a customized bulleted list marker. public func bulletedListMarker( - @ViewBuilder body: @escaping (_ configuration: ListMarkerConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ configuration: ListMarkerConfiguration) -> Body ) -> Theme { var theme = self theme.bulletedListMarker = .init(body: body) @@ -421,7 +421,7 @@ extension Theme { /// Adds a numbered list marker style to the theme. /// - Parameter body: A view builder that returns a customized numbered list marker. public func numberedListMarker( - @ViewBuilder body: @escaping (_ configuration: ListMarkerConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ configuration: ListMarkerConfiguration) -> Body ) -> Theme { var theme = self theme.numberedListMarker = .init(body: body) @@ -431,7 +431,7 @@ extension Theme { /// Adds a table style to the theme. /// - Parameter body: A view builder that returns a customized table. public func table( - @ViewBuilder body: @escaping (_ label: BlockConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration) -> Body ) -> Theme { var theme = self theme.table = .init(body: body) @@ -441,7 +441,7 @@ extension Theme { /// Adds a table cell style to the theme. /// - Parameter body: A view builder that returns a customized table cell. public func tableCell( - @ViewBuilder body: @escaping (_ configuration: TableCellConfiguration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ configuration: TableCellConfiguration) -> Body ) -> Theme { var theme = self theme.tableCell = .init(body: body) @@ -450,7 +450,7 @@ extension Theme { /// Adds a thematic break style to the theme. /// - Parameter body: A view builder that returns a customized thematic break. - public func thematicBreak(@ViewBuilder body: @escaping () -> Body) -> Theme { + public func thematicBreak(@ViewBuilder body: @escaping @Sendable @MainActor () -> Body) -> Theme { var theme = self theme.thematicBreak = .init(body: body) return theme diff --git a/Sources/MarkdownUI/Utility/Deprecations.swift b/Sources/MarkdownUI/Utility/Deprecations.swift index 7a60dffa..01bb34e0 100644 --- a/Sources/MarkdownUI/Utility/Deprecations.swift +++ b/Sources/MarkdownUI/Utility/Deprecations.swift @@ -25,9 +25,9 @@ extension BlockStyle where Configuration == BlockConfiguration { message: "Use the initializer that takes a closure receiving a 'Configuration' value." ) public init( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) { - self.init { configuration in + self.init { @Sendable @MainActor configuration in body(configuration.label) } } @@ -38,7 +38,7 @@ extension BlockStyle where Configuration == BlockConfiguration { message: "Use the initializer that takes a closure receiving a 'Configuration' value." ) public init() { - self.init { $0 } + self.init { @Sendable @MainActor in $0 } } } @@ -53,9 +53,11 @@ extension View { ) public func markdownBlockStyle( _ keyPath: WritableKeyPath>, - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> some View { - self.environment((\EnvironmentValues.theme).appending(path: keyPath), .init(body: body)) + self.transformEnvironment(\.theme) { theme in + theme[keyPath: keyPath] = .init(body: body) + } } @available( @@ -68,14 +70,13 @@ extension View { ) public func markdownBlockStyle( _ keyPath: WritableKeyPath>, - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> some View { - self.environment( - (\EnvironmentValues.theme).appending(path: keyPath), - .init { configuration in - body(.init(configuration.label)) + self.transformEnvironment(\.theme) { theme in + theme[keyPath: keyPath] = .init { configuration in + body(BlockConfiguration.Label(configuration.label)) } - ) + } } } @@ -89,7 +90,7 @@ extension Theme { """ ) public func heading1( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.heading1 = .init(body: body) @@ -105,7 +106,7 @@ extension Theme { """ ) public func heading2( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.heading2 = .init(body: body) @@ -121,7 +122,7 @@ extension Theme { """ ) public func heading3( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.heading3 = .init(body: body) @@ -137,7 +138,7 @@ extension Theme { """ ) public func heading4( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.heading4 = .init(body: body) @@ -153,7 +154,7 @@ extension Theme { """ ) public func heading5( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.heading5 = .init(body: body) @@ -169,7 +170,7 @@ extension Theme { """ ) public func heading6( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.heading6 = .init(body: body) @@ -185,7 +186,7 @@ extension Theme { """ ) public func paragraph( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.paragraph = .init(body: body) @@ -201,7 +202,7 @@ extension Theme { """ ) public func blockquote( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.blockquote = .init(body: body) @@ -217,7 +218,7 @@ extension Theme { """ ) public func codeBlock( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.codeBlock = .init { configuration in @@ -235,7 +236,7 @@ extension Theme { """ ) public func image( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.image = .init(body: body) @@ -251,7 +252,7 @@ extension Theme { """ ) public func list( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.list = .init(body: body) @@ -267,7 +268,7 @@ extension Theme { """ ) public func listItem( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.listItem = .init(body: body) @@ -283,7 +284,7 @@ extension Theme { """ ) public func table( - @ViewBuilder body: @escaping (_ label: BlockConfiguration.Label) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ label: BlockConfiguration.Label) -> Body ) -> Theme { var theme = self theme.table = .init(body: body) diff --git a/Sources/MarkdownUI/Utility/RelativeSize.swift b/Sources/MarkdownUI/Utility/RelativeSize.swift index 36ee9bfb..95626810 100644 --- a/Sources/MarkdownUI/Utility/RelativeSize.swift +++ b/Sources/MarkdownUI/Utility/RelativeSize.swift @@ -21,8 +21,8 @@ import SwiftUI /// FontSize(.em(2)) /// } /// ``` -public struct RelativeSize: Hashable { - enum Unit: Hashable { +public struct RelativeSize: Hashable, Sendable { + enum Unit: Hashable, Sendable { case em case rem } diff --git a/Sources/MarkdownUI/Views/Blocks/TableBorderSelector.swift b/Sources/MarkdownUI/Views/Blocks/TableBorderSelector.swift index 9b85d8a0..e1a27163 100644 --- a/Sources/MarkdownUI/Views/Blocks/TableBorderSelector.swift +++ b/Sources/MarkdownUI/Views/Blocks/TableBorderSelector.swift @@ -3,8 +3,8 @@ import SwiftUI /// A type that selects the visible borders on a Markdown table. /// /// You use a table border selector to select the visible borders when creating a ``TableBorderStyle``. -public struct TableBorderSelector { - var rectangles: (_ tableBounds: TableBounds, _ borderWidth: CGFloat) -> [CGRect] +public struct TableBorderSelector: @unchecked Sendable { + var rectangles: @Sendable (_ tableBounds: TableBounds, _ borderWidth: CGFloat) -> [CGRect] } extension TableBorderSelector { diff --git a/Sources/MarkdownUI/Views/Environment/Environment+BaseURL.swift b/Sources/MarkdownUI/Views/Environment/Environment+BaseURL.swift index 9eb4f852..92316510 100644 --- a/Sources/MarkdownUI/Views/Environment/Environment+BaseURL.swift +++ b/Sources/MarkdownUI/Views/Environment/Environment+BaseURL.swift @@ -13,9 +13,9 @@ extension EnvironmentValues { } private struct BaseURLKey: EnvironmentKey { - static var defaultValue: URL? = nil + static let defaultValue: URL? = nil } private struct ImageBaseURLKey: EnvironmentKey { - static var defaultValue: URL? = nil + static let defaultValue: URL? = nil } diff --git a/Sources/MarkdownUI/Views/Environment/Environment+ImageProvider.swift b/Sources/MarkdownUI/Views/Environment/Environment+ImageProvider.swift index 909f1e4f..333301af 100644 --- a/Sources/MarkdownUI/Views/Environment/Environment+ImageProvider.swift +++ b/Sources/MarkdownUI/Views/Environment/Environment+ImageProvider.swift @@ -7,7 +7,7 @@ extension View { /// or a custom image provider that you define by creating a type that /// conforms to the ``ImageProvider`` protocol. /// - Returns: A view that uses the specified image provider for itself and its child views. - public func markdownImageProvider(_ imageProvider: I) -> some View { + public func markdownImageProvider(_ imageProvider: I) -> some View where I: Sendable { self.environment(\.imageProvider, .init(imageProvider)) } } diff --git a/Sources/MarkdownUI/Views/Environment/Environment+List.swift b/Sources/MarkdownUI/Views/Environment/Environment+List.swift index 46ea6343..9d9eca58 100644 --- a/Sources/MarkdownUI/Views/Environment/Environment+List.swift +++ b/Sources/MarkdownUI/Views/Environment/Environment+List.swift @@ -13,9 +13,9 @@ extension EnvironmentValues { } private struct ListLevelKey: EnvironmentKey { - static var defaultValue = 0 + static let defaultValue = 0 } private struct TightSpacingEnabledKey: EnvironmentKey { - static var defaultValue = false + static let defaultValue = false } diff --git a/Sources/MarkdownUI/Views/Environment/Environment+Theme.swift b/Sources/MarkdownUI/Views/Environment/Environment+Theme.swift index 7fd986a3..be60fa3b 100644 --- a/Sources/MarkdownUI/Views/Environment/Environment+Theme.swift +++ b/Sources/MarkdownUI/Views/Environment/Environment+Theme.swift @@ -15,7 +15,10 @@ extension View { _ keyPath: WritableKeyPath, @TextStyleBuilder textStyle: () -> S ) -> some View { - self.environment((\EnvironmentValues.theme).appending(path: keyPath), textStyle()) + let style = textStyle() + return self.transformEnvironment(\.theme) { theme in + theme[keyPath: keyPath] = style + } } /// Replaces a specific block style on the current ``Theme`` with a block style initialized with the given body closure. @@ -24,9 +27,11 @@ extension View { /// - body: A view builder that returns the customized block. public func markdownBlockStyle( _ keyPath: WritableKeyPath>, - @ViewBuilder body: @escaping () -> Body + @ViewBuilder body: @escaping @Sendable @MainActor () -> Body ) -> some View { - self.environment((\EnvironmentValues.theme).appending(path: keyPath), .init(body: body)) + self.transformEnvironment(\.theme) { theme in + theme[keyPath: keyPath] = BlockStyle(body: body) + } } /// Replaces a specific block style on the current ``Theme`` with a block style initialized with the given body closure. @@ -35,9 +40,11 @@ extension View { /// - body: A view builder that receives the block configuration and returns the customized block. public func markdownBlockStyle( _ keyPath: WritableKeyPath>, - @ViewBuilder body: @escaping (_ configuration: Configuration) -> Body + @ViewBuilder body: @escaping @Sendable @MainActor (_ configuration: Configuration) -> Body ) -> some View { - self.environment((\EnvironmentValues.theme).appending(path: keyPath), .init(body: body)) + self.transformEnvironment(\.theme) { theme in + theme[keyPath: keyPath] = BlockStyle(body: body) + } } /// Replaces the current ``Theme`` task list marker with the given list marker. @@ -70,5 +77,5 @@ extension EnvironmentValues { } private struct ThemeKey: EnvironmentKey { - static let defaultValue: Theme = .basic + nonisolated(unsafe) static let defaultValue: Theme = Theme() }