@craft-native/ios 0.0.395 → 0.0.408
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -0
- package/package.json +1 -1
- package/templates/CraftApp.swift +268 -12
- package/templates/CraftHybrid.swift +1 -0
- package/templates/CraftNativeActions.swift +3 -1
- package/templates/CraftNativeFlatList.swift +4 -0
- package/templates/CraftNativeMutation.swift +27 -2
- package/templates/CraftNativeScreen.swift +293 -44
- package/templates/CraftNativeUI.swift +89 -0
- package/templates/testing/DriverTests.swift.template +17 -1
|
@@ -517,6 +517,95 @@ enum CraftSymbols {
|
|
|
517
517
|
/// controller, which offers no way to say which style it wants, so the first
|
|
518
518
|
/// call teaches that controller's class to answer from here. Only that exact
|
|
519
519
|
/// class is touched, and only once.
|
|
520
|
+
/// The app's typefaces on UIKit's own chrome. A screen's text follows the
|
|
521
|
+
/// stx tree's `fontFamily`; the navigation bar, the tab bar and bar button
|
|
522
|
+
/// labels are UIKit's, outside that tree, so a branded app drew them in the
|
|
523
|
+
/// system face beside screens in its own. `fontFamily` / `headingFontFamily`
|
|
524
|
+
/// (CSS stacks, as the screens use) put the same faces there, through the
|
|
525
|
+
/// appearance proxies every bar starts from.
|
|
526
|
+
enum CraftChromeFonts {
|
|
527
|
+
/// The packaged family a body or heading stack resolved to; nil keeps the system face.
|
|
528
|
+
private(set) static var body: String?
|
|
529
|
+
private(set) static var heading: String?
|
|
530
|
+
|
|
531
|
+
/// The first family of a CSS stack the app can draw. A generic family
|
|
532
|
+
/// ahead of any packaged one means the system face: nil.
|
|
533
|
+
static func family(_ stack: String?) -> String? {
|
|
534
|
+
guard let stack else { return nil }
|
|
535
|
+
for part in stack.split(separator: ",") {
|
|
536
|
+
let name = part.trimmingCharacters(in: .whitespacesAndNewlines).trimmingCharacters(in: CharacterSet(charactersIn: "\"'"))
|
|
537
|
+
switch name.lowercased() {
|
|
538
|
+
case "": continue
|
|
539
|
+
case "sans-serif", "ui-sans-serif", "system-ui", "-apple-system", "serif", "ui-serif", "monospace", "ui-monospace": return nil
|
|
540
|
+
default: if UIFont(name: name, size: 12) != nil { return name }
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return nil
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/// `family` at a chrome size, bold for a heavy weight and scaled with Dynamic Type.
|
|
547
|
+
static func font(_ family: String?, size: CGFloat, weight: UIFont.Weight, textStyle: UIFont.TextStyle) -> UIFont? {
|
|
548
|
+
guard let family, let custom = UIFont(name: family, size: size) else { return nil }
|
|
549
|
+
var traits = custom.fontDescriptor.symbolicTraits
|
|
550
|
+
if weight >= .semibold { traits.insert(.traitBold) }
|
|
551
|
+
let descriptor = custom.fontDescriptor.withSymbolicTraits(traits) ?? custom.fontDescriptor
|
|
552
|
+
return UIFontMetrics(forTextStyle: textStyle).scaledFont(for: UIFont(descriptor: descriptor, size: size))
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
static var titleFont: UIFont? { font(heading, size: 17, weight: .semibold, textStyle: .headline) }
|
|
556
|
+
static var largeTitleFont: UIFont? { font(heading, size: 34, weight: .bold, textStyle: .largeTitle) }
|
|
557
|
+
static var tabLabelFont: UIFont? { font(body, size: 10, weight: .medium, textStyle: .caption2) }
|
|
558
|
+
static var barButtonFont: UIFont? { font(body, size: 17, weight: .regular, textStyle: .body) }
|
|
559
|
+
|
|
560
|
+
static func configure(_ config: CraftConfig) {
|
|
561
|
+
body = family(config.fontFamily)
|
|
562
|
+
heading = family(config.headingFontFamily) ?? body
|
|
563
|
+
if let title = titleFont, let large = largeTitleFont {
|
|
564
|
+
let bar = UINavigationBar.appearance()
|
|
565
|
+
bar.titleTextAttributes = [.font: title]
|
|
566
|
+
bar.largeTitleTextAttributes = [.font: large]
|
|
567
|
+
}
|
|
568
|
+
if let label = tabLabelFont {
|
|
569
|
+
for state in [UIControl.State.normal, .selected] {
|
|
570
|
+
UITabBarItem.appearance().setTitleTextAttributes([.font: label], for: state)
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if let button = barButtonFont {
|
|
574
|
+
for state in [UIControl.State.normal, .highlighted, .disabled] {
|
|
575
|
+
UIBarButtonItem.appearance().setTitleTextAttributes([.font: button], for: state)
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/// The label face on a tab bar. The Liquid Glass bar reads its labels from
|
|
581
|
+
/// its own appearance and ignores UITabBarItem's proxy, so the face goes on
|
|
582
|
+
/// a copy of the bar's appearance, which keeps its material untouched.
|
|
583
|
+
static func apply(to tabBar: UITabBar) {
|
|
584
|
+
guard let label = tabLabelFont else { return }
|
|
585
|
+
func labelled(_ appearance: UITabBarAppearance) -> UITabBarAppearance {
|
|
586
|
+
let copy = appearance.copy()
|
|
587
|
+
for layout in [copy.stackedLayoutAppearance, copy.inlineLayoutAppearance, copy.compactInlineLayoutAppearance] {
|
|
588
|
+
for state in [layout.normal, layout.selected, layout.focused, layout.disabled] {
|
|
589
|
+
state.titleTextAttributes[.font] = label
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return copy
|
|
593
|
+
}
|
|
594
|
+
tabBar.standardAppearance = labelled(tabBar.standardAppearance)
|
|
595
|
+
if let edge = tabBar.scrollEdgeAppearance { tabBar.scrollEdgeAppearance = labelled(edge) }
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/// The title faces on a bar appearance a screen builds itself (an opaque bar).
|
|
599
|
+
static func apply(to appearance: UINavigationBarAppearance) {
|
|
600
|
+
if let title = titleFont { appearance.titleTextAttributes[.font] = title }
|
|
601
|
+
if let large = largeTitleFont { appearance.largeTitleTextAttributes[.font] = large }
|
|
602
|
+
if let button = barButtonFont {
|
|
603
|
+
appearance.buttonAppearance.normal.titleTextAttributes[.font] = button
|
|
604
|
+
appearance.doneButtonAppearance.normal.titleTextAttributes[.font] = button
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
520
609
|
enum CraftStatusBar {
|
|
521
610
|
static var style: UIStatusBarStyle = .default
|
|
522
611
|
private static var taught = Set<ObjectIdentifier>()
|
|
@@ -17,7 +17,7 @@ import Darwin
|
|
|
17
17
|
/// openurl <url> opens a link through the system, answering "Open in the app?"
|
|
18
18
|
/// universallink <https-url> waits up to 60s for an OS-verified link; never opens a browser
|
|
19
19
|
/// shot <name> screenshot → shots/<name>.png
|
|
20
|
-
/// tree <name> accessibility tree → shots/<name>.txt
|
|
20
|
+
/// tree <name> accessibility tree + identified snapshot values → shots/<name>.txt / <name>-values.json
|
|
21
21
|
/// tap <text> first hittable element whose label contains text
|
|
22
22
|
/// tapif <text> tap it when it is on screen, else carry on (the big timer's "Show the controls")
|
|
23
23
|
/// tapid <identifier>
|
|
@@ -367,6 +367,22 @@ final class DriverTests: XCTestCase {
|
|
|
367
367
|
post("/save?name=\(arg).png", shot.pngRepresentation)
|
|
368
368
|
case "tree":
|
|
369
369
|
post("/save?name=\(arg).txt", Data(app.debugDescription.utf8))
|
|
370
|
+
// debugDescription abbreviates values further than the snapshot.
|
|
371
|
+
// Retain snapshot values separately; long receipts chunk themselves.
|
|
372
|
+
do {
|
|
373
|
+
var values: [[String: String]] = []
|
|
374
|
+
func collect(_ node: XCUIElementSnapshot) {
|
|
375
|
+
if !node.identifier.isEmpty, let value = node.value as? String {
|
|
376
|
+
values.append(["id": node.identifier, "label": node.label, "value": value])
|
|
377
|
+
}
|
|
378
|
+
for child in node.children { collect(child) }
|
|
379
|
+
}
|
|
380
|
+
collect(try app.snapshot())
|
|
381
|
+
let data = try JSONSerialization.data(withJSONObject: values, options: [.sortedKeys])
|
|
382
|
+
post("/save?name=\(arg)-values.json", data)
|
|
383
|
+
} catch {
|
|
384
|
+
XCTFail("Could not retain accessibility values: \(error)")
|
|
385
|
+
}
|
|
370
386
|
case "tap":
|
|
371
387
|
if let target = waitFor(arg, 10) { press(target); note("tap \(arg): ok") }
|
|
372
388
|
else { XCTFail("Element not found"); note("FAIL tap \(arg): NOT FOUND") }
|