Skip to content

Commit

Permalink
Merge pull request #34 from LiYanan2004/visionOS
Browse files Browse the repository at this point in the history
Adds visionOS support
  • Loading branch information
LiYanan2004 authored Oct 24, 2024
2 parents 6158dfa + 62ccf22 commit 953b224
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 19 deletions.
33 changes: 27 additions & 6 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@ jobs:
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest

- name: Download iOS Simulator
run: |
xcodebuild -downloadPlatform "iOS Simulator"
- name: Build for iOS
run: |
xcodebuild clean build \
-scheme ${{ env.PROJECT_SCHEME }} \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 15 Pro'
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
-quiet
build-macos:
runs-on: macos-latest
Expand All @@ -46,7 +51,8 @@ jobs:
xcodebuild clean build \
-scheme ${{ env.PROJECT_SCHEME }} \
-sdk macosx \
-destination 'platform=OS X'
-destination 'platform=OS X' \
-quiet
build-tvos:
runs-on: macos-latest
Expand All @@ -58,13 +64,18 @@ jobs:
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest


- name: Download tvOS Simulator
run: |
xcodebuild -downloadPlatform "tvOS Simulator"
- name: Build for tvOS
run: |
xcodebuild clean build \
-scheme ${{ env.PROJECT_SCHEME }} \
-sdk appletvsimulator \
-destination 'platform=tvOS Simulator,name=Apple TV'
-destination 'platform=tvOS Simulator,name=Apple TV' \
-quiet
build-watchos:
runs-on: macos-latest
Expand All @@ -76,13 +87,18 @@ jobs:
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest

- name: Download watchOS Simulator
run: |
xcodebuild -downloadPlatform "watchOS Simulator"
- name: Build for watchOS
run: |
xcodebuild clean build \
-scheme ${{ env.PROJECT_SCHEME }} \
-sdk watchsimulator \
-destination 'platform=watchOS Simulator,name=Apple Watch Ultra 2 (49mm)'
-destination 'platform=watchOS Simulator,name=Apple Watch Ultra 2 (49mm)' \
-quiet
build-visionos:
runs-on: macos-latest
Expand All @@ -94,10 +110,15 @@ jobs:
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest

- name: Download visionOS Simulator
run: |
xcodebuild -downloadPlatform "visionOS Simulator"
- name: Build for visionOS
run: |
xcodebuild clean build \
-scheme ${{ env.PROJECT_SCHEME }} \
-sdk xros \
-destination 'platform=visionOS Simulator,name=Apple Vision Pro'
-destination 'platform=visionOS Simulator,name=Apple Vision Pro' \
-quiet
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ let package = Package(
.iOS(.v15),
.tvOS(.v15),
.watchOS(.v8),
.visionOS(.v1),
],
products: [
.library(name: "MarkdownView", targets: ["MarkdownView"]),
Expand Down
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ You can use MarkdownView in the following platforms:
* iOS 15.0+
* watchOS 8.0+
* tvOS 15.0+
* visionOS 1.0+

# Highlighted Features

Expand Down Expand Up @@ -114,13 +115,6 @@ MarkdownView(text: markdownText)

The implementation of the block directive is exactly the same way.

# Todos

- [x] watchOS support. (specifically watchOS 8.0+)
- [x] Table support for iOS 15.0, macOS 12.0 and tvOS 15.0.
- [x] Add support for font size adjustments using SwiftUI built-in `.font(_:)` modifier.
- [x] Built-in image providers improvements.

# Swift Package Manager

In your `Package.swift` Swift Package Manager manifest, add the following dependency to your `dependencies` argument:
Expand Down
184 changes: 184 additions & 0 deletions Sources/MarkdownView/Helper/SwiftUI+OnChangeModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import SwiftUI

extension View {

/// Adds a modifier for this view that fires an action when a specific
/// value changes.
///
/// You can use `onChange` to trigger a side effect as the result of a
/// value changing, such as an `Environment` key or a `Binding`.
///
/// The system may call the action closure on the main actor, so avoid
/// long-running tasks in the closure. If you need to perform such tasks,
/// detach an asynchronous background task.
///
/// When the value changes, the new version of the closure will be called,
/// so any captured values will have their values from the time that the
/// observed value has its new value. The old and new observed values are
/// passed into the closure. In the following code example, `PlayerView`
/// passes both the old and new values to the model.
///
/// struct PlayerView: View {
/// var episode: Episode
/// @State private var playState: PlayState = .paused
///
/// var body: some View {
/// VStack {
/// Text(episode.title)
/// Text(episode.showTitle)
/// PlayButton(playState: $playState)
/// }
/// .onChange(of: playState) { oldState, newState in
/// model.playStateDidChange(from: oldState, to: newState)
/// }
/// }
/// }
///
/// - Parameters:
/// - value: The value to check against when determining whether
/// to run the closure.
/// - initial: Whether the action should be run when this view initially
/// appears.
/// - action: A closure to run when the value changes.
/// - oldValue: The old value that failed the comparison check (or the
/// initial value when requested).
/// - newValue: The new value that failed the comparison check.
///
/// - Returns: A view that fires an action when the specified value changes.
@_disfavoredOverload
@available(iOS, introduced: 14.0, deprecated: 17.0)
@available(macOS, introduced: 11.0, deprecated: 14.0)
@available(tvOS, introduced: 14.0, deprecated: 17.0)
@available(watchOS, introduced: 7.0, deprecated: 10.0)
@available(visionOS, unavailable)
public func onChange<E: Equatable>(
of value: E,
initial: Bool = false,
_ action: @escaping (_ oldValue: E, _ newValue: E) -> Void
) -> some View {
modifier(
OnChangeModifier(
value: value,
initial: initial,
action: .init(action)
)
)
}

/// Adds a modifier for this view that fires an action when a specific
/// value changes.
///
/// You can use `onChange` to trigger a side effect as the result of a
/// value changing, such as an `Environment` key or a `Binding`.
///
/// The system may call the action closure on the main actor, so avoid
/// long-running tasks in the closure. If you need to perform such tasks,
/// detach an asynchronous background task.
///
/// When the value changes, the new version of the closure will be called,
/// so any captured values will have their values from the time that the
/// observed value has its new value. In the following code example,
/// `PlayerView` calls into its model when `playState` changes model.
///
/// struct PlayerView: View {
/// var episode: Episode
/// @State private var playState: PlayState = .paused
///
/// var body: some View {
/// VStack {
/// Text(episode.title)
/// Text(episode.showTitle)
/// PlayButton(playState: $playState)
/// }
/// .onChange(of: playState) {
/// model.playStateDidChange(state: playState)
/// }
/// }
/// }
///
/// - Parameters:
/// - value: The value to check against when determining whether
/// to run the closure.
/// - initial: Whether the action should be run when this view initially
/// appears.
/// - action: A closure to run when the value changes.
///
/// - Returns: A view that fires an action when the specified value changes.
@_disfavoredOverload
@available(iOS, introduced: 14.0, deprecated: 17.0)
@available(macOS, introduced: 11.0, deprecated: 14.0)
@available(tvOS, introduced: 14.0, deprecated: 17.0)
@available(watchOS, introduced: 7.0, deprecated: 10.0)
@available(visionOS, unavailable)
public func onChange<E: Equatable>(
of value: E,
initial: Bool = false,
_ action: @escaping () -> Void
) -> some View {
modifier(
OnChangeModifier(
value: value,
initial: initial,
action: .init(action)
)
)
}
}

@available(iOS, introduced: 14.0, deprecated: 17.0)
@available(macOS, introduced: 11.0, deprecated: 14.0)
@available(tvOS, introduced: 14.0, deprecated: 17.0)
@available(watchOS, introduced: 7.0, deprecated: 10.0)
@available(visionOS, unavailable)
struct OnChangeModifier<E: Equatable>: ViewModifier {
var value: E
var initial: Bool

struct ActionPack {
var actionWithValues: ((E, E) -> Void)?
var simpleAction: (() -> Void)?

func callAsFunction(before: E, after: E) {
if let actionWithValues {
actionWithValues(before, after)
} else if let simpleAction {
simpleAction()
}
}

init(_ action: @escaping (E, E) -> Void) {
self.actionWithValues = action
self.simpleAction = nil
}

init(_ action: @escaping () -> Void) {
self.simpleAction = action
self.actionWithValues = nil
}
}
var action: ActionPack

func body(content: Content) -> some View {
if #available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) {
content
.onChange(
of: value,
initial: initial,
action.callAsFunction(before:after:)
)
} else {
content
.onAppear(perform: initialAction)
.onChange(of: value) { [value] newValue in
Task { @MainActor in
action(before: value, after: newValue)
}
}
}
}

private func initialAction() {
guard initial else { return }
action(before: value, after: value)
}
}
4 changes: 2 additions & 2 deletions Sources/MarkdownView/MarkdownView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ public struct MarkdownView: View {
// Received a debouncedText, we need to reload MarkdownView.
.onReceive(contentUpdater.textUpdater, perform: updateView(text:))
// Push current text, waiting for next update.
.onChange(of: text, perform: contentUpdater.push(_:))
.onChange(of: text) { contentUpdater.push(text) }
}
.if(renderingMode == .immediate && renderingThread == .background) { content in
content
// Immediately update MarkdownView when text changes.
.onChange(of: text, perform: updateView(text:))
.onChange(of: text) { updateView(text: text) }
}
// Load view immediately after the first launch.
// Receive configuration changes and reload MarkdownView to fit.
Expand Down
6 changes: 3 additions & 3 deletions Sources/MarkdownView/Renderer/Renderer+CodeBlock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ extension Renderer {
extension Renderer {
mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> Result {
Result {
#if os(watchOS) || os(tvOS)
SwiftUI.Text(codeBlock.code)
#else
#if canImport(Highlightr)
HighlightedCodeBlock(
language: codeBlock.language,
code: codeBlock.code,
theme: configuration.codeBlockTheme
)
#else
SwiftUI.Text(codeBlock.code)
#endif
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/MarkdownView/View/SVGView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fileprivate struct _SVGViewBridge: NSViewRepresentable {
Coordinator(updateSize: updateSize)
}
}
#elseif os(iOS)
#elseif os(iOS) || os(visionOS)
fileprivate struct _SVGViewBridge: UIViewRepresentable {
var html: String
var updateSize: (CGSize) -> Void
Expand Down

0 comments on commit 953b224

Please sign in to comment.