Skip to content

Commit 5b6a37f

Browse files
authored
Build UIKitBackend in CI (#119)
* Build UIKitBackend in CI * Fix workflow name * Use matrix instead of for loop * Fix tvOS build by disabling keyboard toolbar * Respell available clause and link to forums
1 parent 70fc5da commit 5b6a37f

File tree

3 files changed

+92
-12
lines changed

3 files changed

+92
-12
lines changed

.github/workflows/swift-uikit.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Build UIKitBackend
2+
3+
on:
4+
push:
5+
branches-ignore:
6+
- 'gh-pages'
7+
pull_request:
8+
branches-ignore:
9+
- 'gh-pages'
10+
11+
jobs:
12+
build-uikit:
13+
runs-on: macos-14
14+
strategy:
15+
matrix:
16+
devicetype:
17+
- iPhone
18+
- iPad
19+
- TV
20+
steps:
21+
- name: Force Xcode 15.4
22+
run: sudo xcode-select -switch /Applications/Xcode_15.4.app
23+
- uses: actions/checkout@v3
24+
- name: Build
25+
run: |
26+
set -uo pipefail
27+
devicetype=${{ matrix.devicetype }}
28+
set +e
29+
deviceid=$(xcrun simctl list devices $devicetype available | grep -v -- -- | tail -n 1 | grep -oE '[0-9A-F\-]{36}')
30+
if [ $? -eq 0 ]; then
31+
set -e
32+
(
33+
buildtarget () {
34+
xcodebuild -scheme "$1" -destination "id=$deviceid" build
35+
}
36+
37+
buildtarget SwiftCrossUI
38+
buildtarget UIKitBackend
39+
40+
cd Examples
41+
42+
buildtarget CounterExample
43+
buildtarget GreetingGeneratorExample
44+
buildtarget NavigationExample
45+
buildtarget StressTestExample
46+
buildtarget NotesExample
47+
48+
if [ $devicetype != TV ]; then
49+
# Slider is not implemented for tvOS
50+
buildtarget ControlsExample
51+
buildtarget RandomNumberGeneratorExample
52+
fi
53+
54+
if [ $devicetype = iPad ]; then
55+
# NavigationSplitView is only implemented for iPad
56+
buildtarget SplitExample
57+
fi
58+
)
59+
else
60+
echo "No $devicetype simulators found" >&2
61+
fi

Sources/UIKitBackend/KeyboardToolbar.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import UIKit
88
/// items will not cause the toolbar to be updated. The toolbar is only updated when the view
99
/// containing the ``View/keyboardToolbar(animateChanges:body:)`` modifier is updated, so any
1010
/// state necessary for the toolbar should live in the view itself.
11+
@available(tvOS, unavailable)
1112
public protocol ToolbarItem {
1213
/// The type of bar button item used to represent this item in UIKit.
1314
associatedtype ItemType: UIBarButtonItem
@@ -19,6 +20,7 @@ public protocol ToolbarItem {
1920
func updateBarButtonItem(_ item: inout ItemType)
2021
}
2122

23+
@available(tvOS, unavailable)
2224
@resultBuilder
2325
public enum ToolbarBuilder {
2426
public enum Component {
@@ -56,6 +58,7 @@ public enum ToolbarBuilder {
5658
}
5759
}
5860

61+
@available(tvOS, unavailable)
5962
extension Button: ToolbarItem {
6063
public final class ItemType: UIBarButtonItem {
6164
var callback: () -> Void
@@ -90,7 +93,11 @@ extension Button: ToolbarItem {
9093
}
9194
}
9295

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

120+
@available(tvOS, unavailable)
113121
struct FixedWidthToolbarItem<Base: ToolbarItem>: ToolbarItem {
114122
var base: Base
115123
var width: Int?
@@ -131,7 +139,8 @@ struct FixedWidthToolbarItem<Base: ToolbarItem>: ToolbarItem {
131139
}
132140

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

@@ -148,6 +157,7 @@ struct FixedWidthSpacerItem: ToolbarItem {
148157
}
149158
}
150159

160+
@available(tvOS, unavailable)
151161
struct ColoredToolbarItem<Base: ToolbarItem>: ToolbarItem {
152162
var base: Base
153163
var color: Color
@@ -164,13 +174,14 @@ struct ColoredToolbarItem<Base: ToolbarItem>: ToolbarItem {
164174
}
165175
}
166176

177+
@available(tvOS, unavailable)
167178
extension ToolbarItem {
168179
/// A toolbar item with the specified width.
169180
///
170181
/// If `width` is positive, the item will have that exact width. If `width` is zero or
171182
/// nil, the item will have its natural size.
172183
public func frame(width: Int?) -> any ToolbarItem {
173-
if #available(iOS 14, macCatalyst 14, tvOS 14, *),
184+
if #available(iOS 14, macCatalyst 14, *),
174185
self is Spacer || self is FixedWidthSpacerItem
175186
{
176187
FixedWidthSpacerItem(width: width)
@@ -185,6 +196,7 @@ extension ToolbarItem {
185196
}
186197
}
187198

199+
@available(tvOS, unavailable)
188200
indirect enum ToolbarItemLocation: Hashable {
189201
case expression(inside: ToolbarItemLocation?)
190202
case block(index: Int, inside: ToolbarItemLocation?)
@@ -194,6 +206,7 @@ indirect enum ToolbarItemLocation: Hashable {
194206
case eitherSecond(inside: ToolbarItemLocation?)
195207
}
196208

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

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

207220
visitItems(component: components, inside: nil) { location, expression in
208-
var item =
221+
let item =
209222
if let oldItem = locations[location] {
210223
updateErasedItem(expression, oldItem)
211224
} else {
@@ -270,10 +283,12 @@ final class KeyboardToolbar: UIToolbar {
270283
}
271284
}
272285

286+
@available(tvOS, unavailable)
273287
enum ToolbarKey: EnvironmentKey {
274288
static let defaultValue: ((KeyboardToolbar) -> Void)? = nil
275289
}
276290

291+
@available(tvOS, unavailable)
277292
extension EnvironmentValues {
278293
var updateToolbar: ((KeyboardToolbar) -> Void)? {
279294
get { self[ToolbarKey.self] }
@@ -287,6 +302,7 @@ extension View {
287302
/// - animateChanges: Whether to animate updates when an item is added, removed, or
288303
/// updated
289304
/// - body: The toolbar's contents
305+
@available(tvOS, unavailable)
290306
public func keyboardToolbar(
291307
animateChanges: Bool = true,
292308
@ToolbarBuilder body: @escaping () -> ToolbarBuilder.FinalResult

Sources/UIKitBackend/UIKitBackend+Control.swift

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -241,14 +241,17 @@ extension UIKitBackend {
241241
textFieldWidget.onChange = onChange
242242
textFieldWidget.onSubmit = onSubmit
243243

244-
if let updateToolbar = environment.updateToolbar {
245-
let toolbar =
246-
(textFieldWidget.child.inputAccessoryView as? KeyboardToolbar) ?? KeyboardToolbar()
247-
updateToolbar(toolbar)
248-
textFieldWidget.child.inputAccessoryView = toolbar
249-
} else {
250-
textFieldWidget.child.inputAccessoryView = nil
251-
}
244+
#if os(iOS)
245+
if let updateToolbar = environment.updateToolbar {
246+
let toolbar =
247+
(textFieldWidget.child.inputAccessoryView as? KeyboardToolbar)
248+
?? KeyboardToolbar()
249+
updateToolbar(toolbar)
250+
textFieldWidget.child.inputAccessoryView = toolbar
251+
} else {
252+
textFieldWidget.child.inputAccessoryView = nil
253+
}
254+
#endif
252255
}
253256

254257
public func setContent(ofTextField textField: Widget, to content: String) {

0 commit comments

Comments
 (0)