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
61 changes: 61 additions & 0 deletions .github/workflows/swift-uikit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Build UIKitBackend

on:
push:
branches-ignore:
- 'gh-pages'
pull_request:
branches-ignore:
- 'gh-pages'

jobs:
build-uikit:
runs-on: macos-14
strategy:
matrix:
devicetype:
- iPhone
- iPad
- TV
steps:
- name: Force Xcode 15.4
run: sudo xcode-select -switch /Applications/Xcode_15.4.app
- uses: actions/checkout@v3
- name: Build
run: |
set -uo pipefail
devicetype=${{ matrix.devicetype }}
set +e
deviceid=$(xcrun simctl list devices $devicetype available | grep -v -- -- | tail -n 1 | grep -oE '[0-9A-F\-]{36}')
if [ $? -eq 0 ]; then
set -e
(
buildtarget () {
xcodebuild -scheme "$1" -destination "id=$deviceid" build
}

buildtarget SwiftCrossUI
buildtarget UIKitBackend

cd Examples

buildtarget CounterExample
buildtarget GreetingGeneratorExample
buildtarget NavigationExample
buildtarget StressTestExample
buildtarget NotesExample

if [ $devicetype != TV ]; then
# Slider is not implemented for tvOS
buildtarget ControlsExample
buildtarget RandomNumberGeneratorExample
fi

if [ $devicetype = iPad ]; then
# NavigationSplitView is only implemented for iPad
buildtarget SplitExample
fi
)
else
echo "No $devicetype simulators found" >&2
fi
24 changes: 20 additions & 4 deletions Sources/UIKitBackend/KeyboardToolbar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import UIKit
/// items will not cause the toolbar to be updated. The toolbar is only updated when the view
/// containing the ``View/keyboardToolbar(animateChanges:body:)`` modifier is updated, so any
/// state necessary for the toolbar should live in the view itself.
@available(tvOS, unavailable)
public protocol ToolbarItem {
/// The type of bar button item used to represent this item in UIKit.
associatedtype ItemType: UIBarButtonItem
Expand All @@ -19,6 +20,7 @@ public protocol ToolbarItem {
func updateBarButtonItem(_ item: inout ItemType)
}

@available(tvOS, unavailable)
@resultBuilder
public enum ToolbarBuilder {
public enum Component {
Expand Down Expand Up @@ -56,6 +58,7 @@ public enum ToolbarBuilder {
}
}

@available(tvOS, unavailable)
extension Button: ToolbarItem {
public final class ItemType: UIBarButtonItem {
var callback: () -> Void
Expand Down Expand Up @@ -90,7 +93,11 @@ extension Button: ToolbarItem {
}
}

@available(iOS 14, macCatalyst 14, tvOS 14, *)
// Despite the fact that this is unavailable on tvOS, the `introduced: 14`
// clause is required for all current Swift versions to accept it.
// See https://forums.swift.org/t/contradictory-available-s-are-required/78831
@available(iOS 14, macCatalyst 14, *)
@available(tvOS, unavailable, introduced: 14)
extension Spacer: ToolbarItem {
public func createBarButtonItem() -> UIBarButtonItem {
if let minLength, minLength > 0 {
Expand All @@ -110,6 +117,7 @@ extension Spacer: ToolbarItem {
}
}

@available(tvOS, unavailable)
struct FixedWidthToolbarItem<Base: ToolbarItem>: ToolbarItem {
var base: Base
var width: Int?
Expand All @@ -131,7 +139,8 @@ struct FixedWidthToolbarItem<Base: ToolbarItem>: ToolbarItem {
}

// Setting width on a flexible space is ignored, you must use a fixed space from the outset
@available(iOS 14, macCatalyst 14, tvOS 14, *)
@available(iOS 14, macCatalyst 14, *)
@available(tvOS, unavailable, introduced: 14)
struct FixedWidthSpacerItem: ToolbarItem {
var width: Int?

Expand All @@ -148,6 +157,7 @@ struct FixedWidthSpacerItem: ToolbarItem {
}
}

@available(tvOS, unavailable)
struct ColoredToolbarItem<Base: ToolbarItem>: ToolbarItem {
var base: Base
var color: Color
Expand All @@ -164,13 +174,14 @@ struct ColoredToolbarItem<Base: ToolbarItem>: ToolbarItem {
}
}

@available(tvOS, unavailable)
extension ToolbarItem {
/// A toolbar item with the specified width.
///
/// If `width` is positive, the item will have that exact width. If `width` is zero or
/// nil, the item will have its natural size.
public func frame(width: Int?) -> any ToolbarItem {
if #available(iOS 14, macCatalyst 14, tvOS 14, *),
if #available(iOS 14, macCatalyst 14, *),
self is Spacer || self is FixedWidthSpacerItem
{
FixedWidthSpacerItem(width: width)
Expand All @@ -185,6 +196,7 @@ extension ToolbarItem {
}
}

@available(tvOS, unavailable)
indirect enum ToolbarItemLocation: Hashable {
case expression(inside: ToolbarItemLocation?)
case block(index: Int, inside: ToolbarItemLocation?)
Expand All @@ -194,6 +206,7 @@ indirect enum ToolbarItemLocation: Hashable {
case eitherSecond(inside: ToolbarItemLocation?)
}

@available(tvOS, unavailable)
final class KeyboardToolbar: UIToolbar {
var locations: [ToolbarItemLocation: UIBarButtonItem] = [:]

Expand All @@ -205,7 +218,7 @@ final class KeyboardToolbar: UIToolbar {
var newLocations: [ToolbarItemLocation: UIBarButtonItem] = [:]

visitItems(component: components, inside: nil) { location, expression in
var item =
let item =
if let oldItem = locations[location] {
updateErasedItem(expression, oldItem)
} else {
Expand Down Expand Up @@ -270,10 +283,12 @@ final class KeyboardToolbar: UIToolbar {
}
}

@available(tvOS, unavailable)
enum ToolbarKey: EnvironmentKey {
static let defaultValue: ((KeyboardToolbar) -> Void)? = nil
}

@available(tvOS, unavailable)
extension EnvironmentValues {
var updateToolbar: ((KeyboardToolbar) -> Void)? {
get { self[ToolbarKey.self] }
Expand All @@ -287,6 +302,7 @@ extension View {
/// - animateChanges: Whether to animate updates when an item is added, removed, or
/// updated
/// - body: The toolbar's contents
@available(tvOS, unavailable)
public func keyboardToolbar(
animateChanges: Bool = true,
@ToolbarBuilder body: @escaping () -> ToolbarBuilder.FinalResult
Expand Down
19 changes: 11 additions & 8 deletions Sources/UIKitBackend/UIKitBackend+Control.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,17 @@ extension UIKitBackend {
textFieldWidget.onChange = onChange
textFieldWidget.onSubmit = onSubmit

if let updateToolbar = environment.updateToolbar {
let toolbar =
(textFieldWidget.child.inputAccessoryView as? KeyboardToolbar) ?? KeyboardToolbar()
updateToolbar(toolbar)
textFieldWidget.child.inputAccessoryView = toolbar
} else {
textFieldWidget.child.inputAccessoryView = nil
}
#if os(iOS)
if let updateToolbar = environment.updateToolbar {
let toolbar =
(textFieldWidget.child.inputAccessoryView as? KeyboardToolbar)
?? KeyboardToolbar()
updateToolbar(toolbar)
textFieldWidget.child.inputAccessoryView = toolbar
} else {
textFieldWidget.child.inputAccessoryView = nil
}
#endif
}

public func setContent(ofTextField textField: Widget, to content: String) {
Expand Down
Loading