Skip to content

Commit 19d772d

Browse files
committed
Added basic UIKit Sheet functionality and preparation for presentation modification
1 parent d13cfaa commit 19d772d

File tree

9 files changed

+176
-17
lines changed

9 files changed

+176
-17
lines changed

Examples/Sources/WindowingExample/WindowingApp.swift

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ struct SheetDemo: View {
8787
.sheet(isPresented: $isShortTermSheetPresented) {
8888
Text("I'm only here for 5s")
8989
.padding(20)
90+
.presentationCornerRadius(2)
9091
}
9192
}
9293

@@ -162,23 +163,24 @@ struct WindowingApp: App {
162163
}
163164
}
164165
}
165-
166-
WindowGroup("Secondary window") {
167-
#hotReloadable {
168-
Text("This a secondary window!")
169-
.padding(10)
166+
#if !os(iOS)
167+
WindowGroup("Secondary window") {
168+
#hotReloadable {
169+
Text("This a secondary window!")
170+
.padding(10)
171+
}
170172
}
171-
}
172-
.defaultSize(width: 200, height: 200)
173-
.windowResizability(.contentMinSize)
173+
.defaultSize(width: 200, height: 200)
174+
.windowResizability(.contentMinSize)
174175

175-
WindowGroup("Tertiary window") {
176-
#hotReloadable {
177-
Text("This a tertiary window!")
178-
.padding(10)
176+
WindowGroup("Tertiary window") {
177+
#hotReloadable {
178+
Text("This a tertiary window!")
179+
.padding(10)
180+
}
179181
}
180-
}
181-
.defaultSize(width: 200, height: 200)
182-
.windowResizability(.contentMinSize)
182+
.defaultSize(width: 200, height: 200)
183+
.windowResizability(.contentMinSize)
184+
#endif
183185
}
184186
}

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,10 @@ public final class AppKitBackend: AppBackend {
17341734
NSApplication.shared.stopModal()
17351735
}
17361736
}
1737+
1738+
public func setPresentationCornerRadius(of sheet: NSCustomSheet, to radius: Int) {
1739+
print("setting Sheet Corner Radius is unavailable on macOS and will be ignored")
1740+
}
17371741
}
17381742

17391743
public final class NSCustomSheet: NSCustomWindow, NSWindowDelegate {

Sources/SwiftCrossUI/Backend/AppBackend.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,27 @@ public protocol AppBackend: Sendable {
633633
/// Gets used by the SCUI sheet implementation to close a sheet.
634634
func dismissSheet(_ sheet: Sheet, window: Window?)
635635

636+
/// Sets the corner radius for a sheet presentation.
637+
///
638+
/// This method is called when the sheet content has a `presentationCornerRadius` modifier
639+
/// applied at its top level. The corner radius affects the sheet's presentation container,
640+
/// not the content itself.
641+
///
642+
/// - Parameters:
643+
/// - sheet: The sheet to apply the corner radius to.
644+
/// - radius: The corner radius in pixels.
645+
func setPresentationCornerRadius(of sheet: Sheet, to radius: Int)
646+
647+
/// Sets the available detents (heights) for a sheet presentation.
648+
///
649+
/// This method is called when the sheet content has a `presentationDetents` modifier
650+
/// applied at its top level. Detents allow users to resize the sheet to predefined heights.
651+
///
652+
/// - Parameters:
653+
/// - sheet: The sheet to apply the detents to.
654+
/// - detents: An array of detents that the sheet can be resized to.
655+
func setPresentationDetents(of sheet: Sheet, to detents: [PresentationDetent])
656+
636657
/// Presents an 'Open file' dialog to the user for selecting files or
637658
/// folders.
638659
///
@@ -1215,4 +1236,12 @@ extension AppBackend {
12151236
public func dismissSheet(_ sheet: Sheet, window: Window?) {
12161237
todo()
12171238
}
1239+
1240+
public func setPresentationCornerRadius(of sheet: Sheet, to radius: Int) {
1241+
todo()
1242+
}
1243+
1244+
public func setPresentationDetents(of sheet: Sheet, to detents: [PresentationDetent]) {
1245+
todo()
1246+
}
12181247
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// Represents the available detents (heights) for a sheet presentation.
2+
public enum PresentationDetent: Sendable, Hashable {
3+
/// A detent that represents a medium height sheet.
4+
case medium
5+
6+
/// A detent that represents a large (full-height) sheet.
7+
case large
8+
9+
/// A detent at a custom fractional height of the available space.
10+
/// - Parameter fraction: A value between 0 and 1 representing the fraction of available height.
11+
case fraction(Double)
12+
13+
/// A detent at a specific fixed height in pixels.
14+
/// - Parameter height: The height in pixels.
15+
case height(Int)
16+
}

Sources/SwiftCrossUI/ViewGraph/PreferenceValues.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,27 @@ import Foundation
22

33
public struct PreferenceValues: Sendable {
44
public static let `default` = PreferenceValues(
5-
onOpenURL: nil
5+
onOpenURL: nil,
6+
presentationDetents: nil,
7+
presentationCornerRadius: nil
68
)
79

810
public var onOpenURL: (@Sendable @MainActor (URL) -> Void)?
911

10-
public init(onOpenURL: (@Sendable @MainActor (URL) -> Void)?) {
12+
/// The available detents for a sheet presentation. Only applies to the top-level view in a sheet.
13+
public var presentationDetents: [PresentationDetent]?
14+
15+
/// The corner radius for a sheet presentation. Only applies to the top-level view in a sheet.
16+
public var presentationCornerRadius: Int?
17+
18+
public init(
19+
onOpenURL: (@Sendable @MainActor (URL) -> Void)?,
20+
presentationDetents: [PresentationDetent]? = nil,
21+
presentationCornerRadius: Int? = nil
22+
) {
1123
self.onOpenURL = onOpenURL
24+
self.presentationDetents = presentationDetents
25+
self.presentationCornerRadius = presentationCornerRadius
1226
}
1327

1428
public init(merging children: [PreferenceValues]) {
@@ -21,5 +35,10 @@ public struct PreferenceValues: Sendable {
2135
}
2236
}
2337
}
38+
39+
// For presentation modifiers, take the first (top-level) value only
40+
// This ensures only the root view's presentation modifiers apply to the sheet
41+
presentationDetents = children.first?.presentationDetents
42+
presentationCornerRadius = children.first?.presentationCornerRadius
2443
}
2544
}

Sources/SwiftCrossUI/Views/Modifiers/SheetModifier.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ struct SheetModifier<Content: View, SheetContent: View>: TypeSafeView {
7878
dryRun: true
7979
)
8080

81+
// Extract preferences from the sheet content
82+
let preferences = dryRunResult.preferences
83+
8184
let sheetSize = dryRunResult.size.idealSize
8285

8386
let _ = children.sheetContentNode.update(
@@ -94,6 +97,16 @@ struct SheetModifier<Content: View, SheetContent: View>: TypeSafeView {
9497
content: children.sheetContentNode.widget.into(),
9598
onDismiss: handleDismiss
9699
)
100+
101+
// Apply presentation preferences to the sheet
102+
if let cornerRadius = preferences.presentationCornerRadius {
103+
backend.setPresentationCornerRadius(of: sheet, to: cornerRadius)
104+
}
105+
106+
if let detents = preferences.presentationDetents {
107+
backend.setPresentationDetents(of: sheet, to: detents)
108+
}
109+
97110
backend.showSheet(
98111
sheet,
99112
window: .some(environment.window! as! Backend.Window)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// PresentationCornerRadiusModifier.swift
3+
// swift-cross-ui
4+
//
5+
// Created by Mia Koring on 03.10.25.
6+
//
7+
8+
extension View {
9+
/// Sets the corner radius for a sheet presentation.
10+
///
11+
/// This modifier only affects the sheet presentation itself when applied to the
12+
/// top-level view within a sheet. It does not affect the content's corner radius.
13+
///
14+
/// - Parameter radius: The corner radius in pixels.
15+
/// - Returns: A view with the presentation corner radius preference set.
16+
public func presentationCornerRadius(_ radius: Int) -> some View {
17+
preference(key: \.presentationCornerRadius, value: radius)
18+
}
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
extension View {
2+
/// Sets the available detents (heights) for a sheet presentation.
3+
///
4+
/// This modifier only affects the sheet presentation itself when applied to the
5+
/// top-level view within a sheet. It allows users to resize the sheet to different
6+
/// predefined heights.
7+
///
8+
/// - Parameter detents: A set of detents that the sheet can be resized to.
9+
/// - Returns: A view with the presentation detents preference set.
10+
public func presentationDetents(_ detents: Set<PresentationDetent>) -> some View {
11+
preference(key: \.presentationDetents, value: Array(detents))
12+
}
13+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import SwiftCrossUI
2+
import UIKit
3+
4+
extension UIKitBackend {
5+
public typealias Sheet = CustomSheet
6+
7+
public func createSheet() -> CustomSheet {
8+
let sheet = CustomSheet()
9+
sheet.modalPresentationStyle = .formSheet
10+
//sheet.transitioningDelegate = CustomSheetTransitioningDelegate()
11+
12+
return sheet
13+
}
14+
15+
public func updateSheet(_ sheet: CustomSheet, content: Widget, onDismiss: @escaping () -> Void)
16+
{
17+
sheet.view = content.view
18+
sheet.onDismiss = onDismiss
19+
}
20+
21+
public func showSheet(_ sheet: CustomSheet, window: UIWindow?) {
22+
var topController = window?.rootViewController
23+
while let presented = topController?.presentedViewController {
24+
topController = presented
25+
}
26+
topController?.present(sheet, animated: true)
27+
}
28+
29+
public func dismissSheet(_ sheet: CustomSheet, window: UIWindow?) {
30+
sheet.dismiss(animated: true)
31+
}
32+
}
33+
34+
public final class CustomSheet: UIViewController {
35+
var onDismiss: (() -> Void)?
36+
37+
public override func viewDidLoad() {
38+
super.viewDidLoad()
39+
}
40+
41+
public override func viewDidDisappear(_ animated: Bool) {
42+
onDismiss?()
43+
}
44+
}

0 commit comments

Comments
 (0)