From 9510365e6dbdb84f34a6b58d802088de5bb46050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=B4=E9=93=AD?= Date: Tue, 13 Aug 2024 09:14:00 +0800 Subject: [PATCH] some clean --- SwiftPamphletApp.xcodeproj/project.pbxproj | 1220 ----------------- .../Guide/View/GuideListView.swift | 3 +- ...51\207\217\346\225\260\346\215\256(ap).md" | 17 - ...SwiftData-\346\243\200\347\264\242(ap).md" | 97 -- ...45\236\213\345\205\263\347\263\273(ap).md" | 31 - ...46\234\254\350\277\201\347\247\273(ap).md" | 64 - ...SwiftData-\350\260\203\350\257\225(ap).md" | 6 - ...SwiftData-\350\265\204\346\226\231(ap).md" | 9 - ...45\244\232\347\272\277\347\250\213(ap).md" | 27 - ...\272@Model\346\250\241\345\236\213(ap).md" | 121 -- ...45\242\236\345\210\240modelContext(ap).md" | 61 - ...\205\215\347\275\256modelContainer(ap).md" | 164 --- ...44\271\211\346\240\267\345\274\217(ap).md" | 38 - ...46\225\260\346\215\256\346\265\201(ap).md" | 137 -- .../Guide/appstore/SwiftUI/ViewBuilder(ap).md | 117 -- ...50\200\203\350\265\204\346\226\231(ap).md" | 17 - ...2\204UIKit\350\247\206\345\233\276(ap).md" | 26 - ...46\230\257\344\273\200\344\271\210(ap).md" | 53 - .../ContainerRelativeShape(ap).md" | 43 - ...47\247\201\345\261\225\347\244\272(ap).md" | 38 - ...\351\245\260\347\254\246-fixedSize(ap).md" | 65 - ...1\245\260\347\254\246-visualEffect(ap).md" | 47 - ...7\254\246-\345\234\206\350\247\222(ap).md" | 63 - ...7\254\246-\350\222\231\347\211\210(ap).md" | 89 -- ...44\277\256\351\245\260\347\254\246(ap).md" | 35 - ...44\277\256\351\245\260\347\254\246(ap).md" | 77 -- .../Image(ap).md" | 72 - .../Label(ap).md" | 93 -- .../Link(ap).md" | 88 -- .../Text/Text(ap).md" | 388 ------ ...46\200\201\346\227\266\351\227\264(ap).md" | 43 - .../TextEditor(ap).md" | 195 --- .../TextField(ap).md" | 109 -- .../Advanced layout control(ap).md" | 202 --- .../ContentUnavailableView(ap).md" | 56 - .../ControlGroup(ap).md" | 21 - .../GroupBox(ap).md" | 137 -- ...45\207\272\344\270\200\346\240\217(ap).md" | 68 - .../Navigation(ap).md" | 266 ---- .../NavigationPath(ap).md" | 80 -- .../NavigationSplitView(ap).md" | 50 - .../NavigationStack(ap).md" | 115 -- ...45\222\214\350\277\230\345\216\237(ap).md" | 104 -- ...45\257\274\350\210\252\346\240\217(ap).md" | 52 - .../Stack(ap).md" | 38 - .../TabView(ap).md" | 194 --- .../Safe Area(ap).md" | 105 -- ...200-offset\345\201\217\347\247\273(ap).md" | 19 - ...5\261\200-\345\237\272\347\241\200(ap).md" | 43 - ...5\261\200-\345\257\271\351\275\220(ap).md" | 32 - ...5\261\200-\345\261\205\344\270\255(ap).md" | 34 - ...5\261\200-\347\225\231\347\231\275(ap).md" | 36 - ...45\261\200\345\216\237\347\220\206(ap).md" | 12 - .../AnyLayout(ap).md" | 42 - .../GeometryReader(ap).md" | 71 - .../Layout\345\215\217\350\256\256(ap).md" | 42 - .../ViewThatFits(ap).md" | 78 -- .../alignmentGuide(ap).md" | 34 - ...50\200\203\350\265\204\346\226\231(ap).md" | 18 - .../ForEach(ap).md" | 136 -- .../Grid(ap).md" | 71 - .../LazyVGrid\345\222\214LazyHGrid(ap).md" | 69 - .../LazyVStack\345\222\214LazyHStack(ap).md" | 26 - .../List\345\210\227\350\241\250/List(ap).md" | 265 ---- ...46\213\211\345\210\267\346\226\260(ap).md" | 28 - ...50\275\275\346\233\264\345\244\232(ap).md" | 34 - ...47\272\262\350\247\206\345\233\276(ap).md" | 123 -- ...45\207\273\347\232\204\350\241\214(ap).md" | 21 - .../List-\346\220\234\347\264\242(ap).md" | 184 --- ...45\212\250\345\205\203\347\264\240(ap).md" | 29 - ...45\274\225\346\240\207\351\242\230(ap).md" | 72 - ...47\275\256\346\240\267\345\274\217(ap).md" | 132 -- ...46\211\253\346\223\215\344\275\234(ap).md" | 29 - .../ScrollView(ap).md" | 99 -- ...50\200\203\350\265\204\346\226\231(ap).md" | 10 - ...51\241\265\346\273\232\345\212\250(ap).md" | 85 -- ...50\247\211\346\225\210\346\236\234(ap).md" | 117 -- ...47\232\204\351\241\266\351\203\250(ap).md" | 17 - ...47\232\204\344\275\215\347\275\256(ap).md" | 101 -- .../Table(ap).md" | 53 - .../Table-contextMenu(ap).md" | 30 - ...46\200\247\346\216\222\345\272\217(ap).md" | 61 - .../Table-\346\240\267\345\274\217(ap).md" | 18 - ...47\232\204\351\200\211\346\213\251(ap).md" | 41 - .../Alert(ap).md" | 27 - .../Full Screen Modal View(ap).md" | 43 - .../HUD(ap).md" | 40 - .../Menu\345\222\214ContextMenu(ap).md" | 78 -- .../Popover(ap).md" | 44 - .../Sheet(ap).md" | 373 ----- .../confirmationDialog()(ap).md" | 34 - .../\346\265\256\345\261\202(ap).md" | 156 --- .../\350\241\250\345\215\225/Form(ap).md" | 137 -- .../ColorPicker(ap).md" | 22 - .../DatePicker(ap).md" | 62 - .../PhotoPicker(ap).md" | 131 -- .../Picker(ap).md" | 109 -- .../WheelPicker(ap).md" | 62 - .../\345\255\227\344\275\223Picker(ap).md" | 39 - .../\346\226\207\345\255\227Picker(ap).md" | 119 -- .../\350\241\250\345\215\225/Slider(ap).md" | 58 - .../\350\241\250\345\215\225/Stepper(ap).md" | 22 - .../\350\241\250\345\215\225/Toggle(ap).md" | 209 --- ...Animations\345\215\217\350\256\256(ap).md" | 74 - .../Documents\345\215\217\350\256\256(ap).md" | 61 - ...cy bridges\345\215\217\350\256\256(ap).md" | 62 - .../Previews\345\215\217\350\256\256(ap).md" | 43 - ...nder chain\345\215\217\350\256\256(ap).md" | 59 - .../Shapes\345\215\217\350\256\256(ap).md" | 43 - .../Style\345\215\217\350\256\256(ap).md" | 55 - .../Toolbar\345\215\217\350\256\256(ap).md" | 57 - ...44\273\266\345\215\217\350\256\256(ap).md" | 42 - ...45\233\276\345\215\217\350\256\256(ap).md" | 101 -- ...45\215\217\350\256\256-Environment(ap).md" | 48 - ...45\277\203\345\215\217\350\256\256(ap).md" | 60 - ...0\256\256-\347\256\200\344\273\213(ap).md" | 28 - .../Button(ap).md" | 280 ---- .../Keyboard(ap).md" | 27 - .../ShareLink(ap).md" | 14 - .../Transferable(ap).md" | 25 - .../\350\277\233\345\272\246(ap).md" | 131 -- .../Blend Modes(ap).md" | 83 -- .../SF Symbol(ap).md" | 168 --- .../Shaders Metal(ap).md" | 4 - .../SwiftCharts(ap).md" | 143 -- .../SwiftUI Canvas(ap).md" | 140 -- .../SwiftUI Effect(ap).md" | 73 - .../SwiftUI-Shadow(ap).md" | 67 - .../SwiftUI-\346\250\241\347\263\212(ap).md" | 5 - .../SwiftUI-\346\270\220\345\217\230(ap).md" | 96 -- ...46\231\257\346\235\220\350\264\250(ap).md" | 77 -- .../SwiftUI\351\242\234\350\211\262(ap).md" | 48 - ...44\272\253\350\217\234\345\215\225(ap).md" | 29 - ...45\211\252\350\264\264\346\235\277(ap).md" | 14 - ...46\234\257\346\274\224\350\277\233(ap).md" | 90 -- .../macOS\350\214\203\344\276\213(ap).md" | 2 - ...46\240\217\347\273\223\346\236\204(ap).md" | 74 - ...45\261\217\346\250\241\345\274\217(ap).md" | 42 - ...47\232\204\344\275\277\347\224\250(ap).md" | 184 --- .../KeyframeAnimator(ap).md" | 115 -- .../Matched Geometry Effect(ap).md" | 455 ------ .../PhaseAnimator(ap).md" | 55 - .../SwiftUI\345\212\250\347\224\273(ap).md" | 304 ---- .../Transaction(ap).md" | 357 ----- ...44\277\256\351\245\260\347\254\246(ap).md" | 298 ---- .../contentTransition(ap).md" | 19 - ...7\224\273-\344\276\213\345\255\220(ap).md" | 2 - ...45\261\200\345\212\250\347\224\273(ap).md" | 31 - .../AttributeString(ap).md" | 67 - .../Data(ap).md" | 23 - .../Scanner(ap).md" | 31 - .../UserDefaults(ap).md" | 11 - ...45\272\246\351\207\217\345\200\274(ap).md" | 28 - .../\346\226\207\344\273\266(ap).md" | 77 -- .../\346\227\266\351\227\264(ap).md" | 51 - ...46\240\274\345\274\217\345\214\226(ap).md" | 91 -- ...45\272\246\351\207\217\345\200\274(ap).md" | 51 - ...5\214\226-\346\225\260\346\215\256(ap).md" | 34 - ...5\214\226-\346\227\266\351\227\264(ap).md" | 140 -- ...46\264\273\346\227\245\345\270\270(ap).md" | 47 - ...45\217\257\344\275\277\347\224\250(ap).md" | 7 - ...47\232\204\345\210\244\346\226\255(ap).md" | 7 - ...46\234\254\345\205\274\345\256\271(ap).md" | 14 - ...47\273\237\345\210\244\346\226\255(ap).md" | 9 - .../Hashable(ap).md" | 31 - ...\234\211id\345\255\227\346\256\265(ap).md" | 14 - ...47\224\250\347\261\273\345\236\213(ap).md" | 28 - ...45\221\230\346\237\245\350\257\242(ap).md" | 45 - .../@resultBuilder(ap).md" | 69 - .../\351\232\217\346\234\272(ap).md" | 8 - ...46\230\257\344\273\200\344\271\210(ap).md" | 56 - ...47\232\204\350\265\204\346\226\231(ap).md" | 15 - .../Combine KVO(ap).md" | 13 - .../Combine Timer(ap).md" | 8 - ...47\273\234\350\257\267\346\261\202(ap).md" | 362 ----- .../Combine\351\200\232\347\237\245(ap).md" | 26 - .../CurrentValueSubject(ap).md" | 52 - .../Empty(ap).md" | 37 - .../Just(ap).md" | 27 - .../PassthroughSubject(ap).md" | 79 -- .../Scheduler(ap).md" | 35 - .../append(ap).md" | 36 - .../combineLatest(ap).md" | 40 - .../flatMap(ap).md" | 41 - .../merge(ap).md" | 40 - .../prepend(ap).md" | 44 - .../publisher(ap).md" | 29 - .../removeDuplicates(ap).md" | 36 - .../zip(ap).md" | 37 - .../Swift Concurrency/Actors(ap).md" | 9 - .../Swift Concurrency/Async Sequences(ap).md" | 9 - ...46\234\257\346\274\224\350\277\233(ap).md" | 49 - .../Distributed Actors(ap).md" | 8 - ...ift Concurrency\345\222\214Combine(ap).md" | 1 - ...44\271\240\350\267\257\345\276\204(ap).md" | 1 - ...46\230\257\344\273\200\344\271\210(ap).md" | 22 - ...45\205\263\346\217\220\346\241\210(ap).md" | 16 - .../Swift Concurrency/async await(ap).md" | 3 - ...45\214\226\345\271\266\345\217\221(ap).md" | 3 - .../\345\256\211\345\205\250/Keychain(ap).md" | 51 - .../AppIntentTimelineProvider(ap).md" | 37 - .../Widget View(ap).md" | 120 -- ...45\260\217\347\273\204\344\273\266(ap).md" | 87 -- ...344\273\266-AppIntentConfiguration(ap).md" | 85 -- ...\347\273\204\344\273\266-Deep link(ap).md" | 3 - ...04\344\273\266-StaticConfiguration(ap).md" | 64 - ...50\200\203\350\265\204\346\226\231(ap).md" | 42 - ...46\226\260\345\206\205\345\256\271(ap).md" | 7 - ...45\217\226\346\225\260\346\215\256(ap).md" | 17 - ...47\275\256\351\200\211\351\241\271(ap).md" | 75 - ...44\273\266\345\212\250\347\224\273(ap).md" | 22 - ...6\350\256\277\351\227\256SwiftData(ap).md" | 7 - ...45\260\217\347\273\204\344\273\266(ap).md" | 27 - ...44\273\266\345\275\242\347\212\266(ap).md" | 15 - .../\345\215\225\344\276\213(ap).md" | 8 - ...45\205\245\345\217\243\347\202\271(ap).md" | 28 - ...45\255\230\347\256\241\347\220\206(ap).md" | 3 - ...46\234\257\346\274\224\350\277\233(ap).md" | 28 - .../\350\260\203\350\257\225(ap).md" | 10 - ...51\223\276\346\216\245\345\231\250(ap).md" | 38 - .../AppIcon(ap).md" | 37 - .../Swift-DocC(ap).md" | 8 - ...46\200\201\346\243\200\346\237\245(ap).md" | 75 - 223 files changed, 1 insertion(+), 16313 deletions(-) delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\345\244\204\347\220\206\345\244\247\351\207\217\346\225\260\346\215\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\346\243\200\347\264\242(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\346\250\241\345\236\213\345\205\263\347\263\273(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\347\211\210\346\234\254\350\277\201\347\247\273(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\350\260\203\350\257\225(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\350\265\204\346\226\231(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData\345\244\232\347\272\277\347\250\213(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\210\233\345\273\272@Model\346\250\241\345\236\213(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\242\236\345\210\240modelContext(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\256\271\345\231\250\351\205\215\347\275\256modelContainer(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/SwiftUI-\350\207\252\345\256\232\344\271\211\346\240\267\345\274\217(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/SwiftUI\346\225\260\346\215\256\346\265\201(ap).md" delete mode 100644 SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/ViewBuilder(ap).md delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\345\257\271\346\240\207\347\232\204UIKit\350\247\206\345\233\276(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\346\230\257\344\273\200\344\271\210(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/ContainerRelativeShape(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/redacted\351\232\220\347\247\201\345\261\225\347\244\272(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-fixedSize(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-visualEffect(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-\345\234\206\350\247\222(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-\350\222\231\347\211\210(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\350\203\214\346\231\257\344\277\256\351\245\260\347\254\246(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\350\207\252\345\256\232\344\271\211\344\277\256\351\245\260\347\254\246(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Image(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Label(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Link(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Text/Text(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Text/Text-\345\212\250\346\200\201\346\227\266\351\227\264(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/TextEditor(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/TextField(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Advanced layout control(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/ContentUnavailableView(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/ControlGroup(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/GroupBox(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/Inspectors\345\217\263\344\276\247\345\244\232\345\207\272\344\270\200\346\240\217(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/Navigation(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationPath(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationSplitView(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationStack(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/\345\257\274\350\210\252\347\212\266\346\200\201\344\277\235\345\255\230\345\222\214\350\277\230\345\216\237(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/\350\207\252\345\256\232\344\271\211\345\257\274\350\210\252\346\240\217(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Stack(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/TabView(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/Safe Area(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-offset\345\201\217\347\247\273(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\237\272\347\241\200(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\257\271\351\275\220(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\261\205\344\270\255(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\347\225\231\347\231\275(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200\345\216\237\347\220\206(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/AnyLayout(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/GeometryReader(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/Layout\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/ViewThatFits(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/alignmentGuide(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/\345\270\203\345\261\200\350\277\233\351\230\266-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/ForEach(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Grid(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Lazy\345\256\271\345\231\250/LazyVGrid\345\222\214LazyHGrid(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Lazy\345\256\271\345\231\250/LazyVStack\345\222\214LazyHStack(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\344\270\213\346\213\211\345\210\267\346\226\260(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\345\212\240\350\275\275\346\233\264\345\244\232(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\345\244\247\347\272\262\350\247\206\345\233\276(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\345\256\214\345\205\250\345\217\257\347\202\271\345\207\273\347\232\204\350\241\214(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\346\220\234\347\264\242(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\347\247\273\345\212\250\345\205\203\347\264\240(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\347\264\242\345\274\225\346\240\207\351\242\230(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\350\256\276\347\275\256\346\240\267\345\274\217(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\350\275\273\346\211\253\346\223\215\344\275\234(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/ScrollView(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/ScrollView-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/scrollTargetBehavior\345\210\206\351\241\265\346\273\232\345\212\250(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/scrollTransition\350\247\206\350\247\211\346\225\210\346\236\234(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/\345\233\272\345\256\232\345\210\260\346\273\232\345\212\250\350\247\206\345\233\276\347\232\204\351\241\266\351\203\250(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/\346\273\232\345\212\250\345\210\260\347\211\271\345\256\232\347\232\204\344\275\215\347\275\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-contextMenu(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\345\244\232\345\261\236\346\200\247\346\216\222\345\272\217(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\346\240\267\345\274\217(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\350\241\214\347\232\204\351\200\211\346\213\251(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Alert(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Full Screen Modal View(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/HUD(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Menu\345\222\214ContextMenu(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Popover(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Sheet(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/confirmationDialog()(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/\346\265\256\345\261\202(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Form(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/ColorPicker(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/DatePicker(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/PhotoPicker(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/Picker(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/WheelPicker(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/\345\255\227\344\275\223Picker(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/\346\226\207\345\255\227Picker(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Slider(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Stepper(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Toggle(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Animations\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Documents\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Legacy bridges\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Previews\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Responder chain\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Shapes\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Style\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Toolbar\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\345\260\217\347\273\204\344\273\266\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\347\211\271\345\256\232\346\203\205\345\206\265\350\247\206\345\233\276\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-Environment(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-\346\240\270\345\277\203\345\215\217\350\256\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-\347\256\200\344\273\213(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Button(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Keyboard(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/ShareLink(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Transferable(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/\350\277\233\345\272\246(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/Blend Modes(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SF Symbol(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/Shaders Metal(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftCharts(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI Canvas(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI Effect(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-Shadow(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\346\250\241\347\263\212(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\346\270\220\345\217\230(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\350\203\214\346\231\257\346\235\220\350\264\250(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI\351\242\234\350\211\262(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\345\205\261\344\272\253\350\217\234\345\215\225(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\345\211\252\350\264\264\346\235\277(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\350\214\203\344\276\213(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/macOS/\344\270\211\346\240\217\347\273\223\346\236\204(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/macOS/\345\205\250\345\261\217\346\250\241\345\274\217(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\344\270\211\346\226\271\345\272\223\344\275\277\347\224\250/SQLite.swift\347\232\204\344\275\277\347\224\250(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/KeyframeAnimator(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/Matched Geometry Effect(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/PhaseAnimator(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/SwiftUI\345\212\250\347\224\273(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/Transaction(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/animation\344\277\256\351\245\260\347\254\246(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/contentTransition(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/\345\212\250\347\224\273-\344\276\213\345\255\220(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/\345\270\203\345\261\200\345\212\250\347\224\273(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/AttributeString(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/Data(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/Scanner(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/UserDefaults(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\345\272\246\351\207\217\345\200\274(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\226\207\344\273\266(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\227\266\351\227\264(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\345\272\246\351\207\217\345\200\274(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\346\225\260\346\215\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\346\227\266\351\227\264(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\347\224\237\346\264\273\346\227\245\345\270\270(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/canImport\345\210\244\346\226\255\345\272\223\346\230\257\345\220\246\345\217\257\344\275\277\347\224\250(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/targetEnvironment\347\216\257\345\242\203\347\232\204\345\210\244\346\226\255(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/\347\211\210\346\234\254\345\205\274\345\256\271(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/\347\263\273\347\273\237\345\210\244\346\226\255(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\215\217\350\256\256/Hashable(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\215\217\350\256\256/JSON\346\262\241\346\234\211id\345\255\227\346\256\265(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@dynamicCallable\345\212\250\346\200\201\345\217\257\350\260\203\347\224\250\347\261\273\345\236\213(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@dynamicMemberLookup\345\212\250\346\200\201\346\210\220\345\221\230\346\237\245\350\257\242(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@resultBuilder(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\351\232\217\346\234\272(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\273\213\347\273\215/Combine\346\230\257\344\273\200\344\271\210(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\273\213\347\273\215/Combine\347\232\204\350\265\204\346\226\231(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine KVO(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine Timer(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine\347\275\221\347\273\234\350\257\267\346\261\202(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine\351\200\232\347\237\245(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/CurrentValueSubject(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Empty(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Just(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/PassthroughSubject(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Scheduler(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/append(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/combineLatest(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/flatMap(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/merge(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/prepend(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/publisher(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/removeDuplicates(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/zip(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Actors(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Async Sequences(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Concurrency\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Distributed Actors(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\345\222\214Combine(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\345\255\246\344\271\240\350\267\257\345\276\204(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\346\230\257\344\273\200\344\271\210(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\347\233\270\345\205\263\346\217\220\346\241\210(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/async await(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/\347\273\223\346\236\204\345\214\226\345\271\266\345\217\221(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\256\211\345\205\250/Keychain(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/AppIntentTimelineProvider(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/Widget View(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\210\267\346\226\260\345\260\217\347\273\204\344\273\266(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-AppIntentConfiguration(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-Deep link(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-StaticConfiguration(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\350\216\267\345\217\226\344\275\215\347\275\256\346\235\203\351\231\220\346\233\264\346\226\260\345\206\205\345\256\271(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\350\277\234\347\250\213\345\256\232\346\227\266\350\216\267\345\217\226\346\225\260\346\215\256(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\351\205\215\347\275\256\351\200\211\351\241\271(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266\345\212\250\347\224\273(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266\350\256\277\351\227\256SwiftData(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\346\224\257\346\214\201\345\244\232\344\270\252\345\260\217\347\273\204\344\273\266(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\350\216\267\345\217\226\345\260\217\347\273\204\344\273\266\345\275\242\347\212\266(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\267\245\347\250\213\346\250\241\345\274\217/\345\215\225\344\276\213(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\345\267\245\347\250\213\346\250\241\345\274\217/\347\250\213\345\272\217\345\205\245\345\217\243\347\202\271(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\345\206\205\345\255\230\347\256\241\347\220\206(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\346\200\247\350\203\275\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\350\260\203\350\257\225(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\351\223\276\346\216\245\345\231\250(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\347\263\273\347\273\237\350\203\275\345\212\233/AppIcon(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\347\263\273\347\273\237\350\203\275\345\212\233/Swift-DocC(ap).md" delete mode 100644 "SwiftPamphletApp/Resource/Guide/appstore/\347\275\221\347\273\234/\347\275\221\347\273\234\347\212\266\346\200\201\346\243\200\346\237\245(ap).md" diff --git a/SwiftPamphletApp.xcodeproj/project.pbxproj b/SwiftPamphletApp.xcodeproj/project.pbxproj index 140f32bf0..db92e5418 100644 --- a/SwiftPamphletApp.xcodeproj/project.pbxproj +++ b/SwiftPamphletApp.xcodeproj/project.pbxproj @@ -7,30 +7,10 @@ objects = { /* Begin PBXBuildFile section */ - 08026C412869B1BF00792EF1 /* 性能技术演进(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C402869B1BF00792EF1 /* 性能技术演进(ap).md */; }; 08026C432869B22E00792EF1 /* Regex(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C422869B22E00792EF1 /* Regex(ap).md */; }; - 08026C442869B24F00792EF1 /* Concurrency技术演进(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C2F2869945300792EF1 /* Concurrency技术演进(ap).md */; }; - 08026C452869B25700792EF1 /* Distributed Actors(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C302869948400792EF1 /* Distributed Actors(ap).md */; }; - 08026C462869B26000792EF1 /* Swift-DocC(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C322869991200792EF1 /* Swift-DocC(ap).md */; }; - 08026C472869B26900792EF1 /* 调试(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C34286999BB00792EF1 /* 调试(ap).md */; }; - 08026C482869B28A00792EF1 /* 内存管理(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C3528699AAA00792EF1 /* 内存管理(ap).md */; }; - 08026C492869B38300792EF1 /* 链接器(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C3628699C8700792EF1 /* 链接器(ap).md */; }; - 08026C4A2869B38F00792EF1 /* SwiftUI参考资料(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C3828699EB700792EF1 /* SwiftUI参考资料(ap).md */; }; - 08026C4B2869B39700792EF1 /* SwiftCharts(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C392869A17800792EF1 /* SwiftCharts(ap).md */; }; - 08026C4C2869B39F00792EF1 /* Advanced layout control(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C3A2869A21000792EF1 /* Advanced layout control(ap).md */; }; - 08026C4D2869B3A600792EF1 /* Transferable(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C3B2869A2F100792EF1 /* Transferable(ap).md */; }; - 08026C4E2869B3B500792EF1 /* ShareLink(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C3C2869A35800792EF1 /* ShareLink(ap).md */; }; - 08026C4F2869B3BE00792EF1 /* Form(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C3D2869A51200792EF1 /* Form(ap).md */; }; - 08026C512869B43500792EF1 /* Table(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C502869B41B00792EF1 /* Table(ap).md */; }; - 08026C522869B43D00792EF1 /* SF Symbol(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C3E2869AD7800792EF1 /* SF Symbol(ap).md */; }; - 08026C532869B44400792EF1 /* macOS技术演进(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08026C3F2869B00D00792EF1 /* macOS技术演进(ap).md */; }; 08069CAB2BDE01E800D48E24 /* GuideListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08069CAA2BDE01E800D48E24 /* GuideListView.swift */; }; 08069CAD2BDE7A6B00D48E24 /* GuideDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08069CAC2BDE7A6B00D48E24 /* GuideDetailView.swift */; }; 0820367A2C02EEA7002FB5E3 /* SMUI in Frameworks */ = {isa = PBXBuildFile; productRef = 082036792C02EEA7002FB5E3 /* SMUI */; }; - 082036802C073C0E002FB5E3 /* 格式化-时间(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0820367F2C073C0E002FB5E3 /* 格式化-时间(ap).md */; }; - 082036822C073C24002FB5E3 /* 格式化-数据(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 082036812C073C24002FB5E3 /* 格式化-数据(ap).md */; }; - 082036842C075A01002FB5E3 /* 格式化-度量值(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 082036832C075A01002FB5E3 /* 格式化-度量值(ap).md */; }; - 082036862C075A18002FB5E3 /* 格式化-生活日常(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 082036852C075A18002FB5E3 /* 格式化-生活日常(ap).md */; }; 0825E4872BC6596F00332378 /* EditCustomSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0825E4862BC6596F00332378 /* EditCustomSearchView.swift */; }; 08397E252B9EEE1300DFDD02 /* InfoListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08397E242B9EEE1300DFDD02 /* InfoListView.swift */; }; 08397E292B9F0A9100DFDD02 /* EditInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08397E282B9F0A9100DFDD02 /* EditInfoView.swift */; }; @@ -42,30 +22,8 @@ 084417792B99BE720049297D /* DataLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084417782B99BE720049297D /* DataLink.swift */; }; 08448F0F2799328700B61353 /* css_cn.html in Resources */ = {isa = PBXBuildFile; fileRef = 08448F0E2799328700B61353 /* css_cn.html */; }; 08448F4E279E8CA400B61353 /* Ink in Frameworks */ = {isa = PBXBuildFile; productRef = 08448F4D279E8CA400B61353 /* Ink */; }; - 08448F51279E900500B61353 /* macOS剪贴板(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F50279E900500B61353 /* macOS剪贴板(ap).md */; }; - 08448F59279EA84100B61353 /* macOS范例(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F56279EA84100B61353 /* macOS范例(ap).md */; }; - 08448F5A279EA84100B61353 /* 三栏结构(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F57279EA84100B61353 /* 三栏结构(ap).md */; }; - 08448F5B279EA84100B61353 /* macOS共享菜单(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F58279EA84100B61353 /* macOS共享菜单(ap).md */; }; - 08448F5E279EB23600B61353 /* SQLite.swift的使用(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F5D279EB23600B61353 /* SQLite.swift的使用(ap).md */; }; 08448F64279EB32C00B61353 /* Swift规范(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F63279EB32C00B61353 /* Swift规范(ap).md */; }; 08448F66279EB33F00B61353 /* Swift书单(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F65279EB33F00B61353 /* Swift书单(ap).md */; }; - 08448F6A279EB47E00B61353 /* 时间(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F69279EB47E00B61353 /* 时间(ap).md */; }; - 08448F6D279EB51000B61353 /* 格式化(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F6C279EB51000B61353 /* 格式化(ap).md */; }; - 08448F6F279EB56400B61353 /* 度量值(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F6E279EB56400B61353 /* 度量值(ap).md */; }; - 08448F71279EB58C00B61353 /* Data(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F70279EB58C00B61353 /* Data(ap).md */; }; - 08448F73279EB5DF00B61353 /* 文件(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F72279EB5DF00B61353 /* 文件(ap).md */; }; - 08448F75279EB62B00B61353 /* Scanner(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F74279EB62B00B61353 /* Scanner(ap).md */; }; - 08448F77279EB65800B61353 /* AttributeString(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F76279EB65800B61353 /* AttributeString(ap).md */; }; - 08448F79279EB68D00B61353 /* 随机(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F78279EB68D00B61353 /* 随机(ap).md */; }; - 08448F7B279EB6CC00B61353 /* UserDefaults(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F7A279EB6CC00B61353 /* UserDefaults(ap).md */; }; - 08448F7E279EB71D00B61353 /* 单例(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F7D279EB71D00B61353 /* 单例(ap).md */; }; - 08448F81279EB75800B61353 /* 系统判断(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F80279EB75800B61353 /* 系统判断(ap).md */; }; - 08448F83279EB78A00B61353 /* 版本兼容(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F82279EB78A00B61353 /* 版本兼容(ap).md */; }; - 08448F86279EB7D200B61353 /* JSON没有id字段(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F85279EB7D200B61353 /* JSON没有id字段(ap).md */; }; - 08448F89279EB7FD00B61353 /* 网络状态检查(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F88279EB7FD00B61353 /* 网络状态检查(ap).md */; }; - 08448F8C279EB84800B61353 /* 布局动画(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F8B279EB84800B61353 /* 布局动画(ap).md */; }; - 08448F8F279EB88500B61353 /* Keychain(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F8E279EB88500B61353 /* Keychain(ap).md */; }; - 08448F92279EB8CA00B61353 /* 程序入口点(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F91279EB8CA00B61353 /* 程序入口点(ap).md */; }; 08448F95279EB96F00B61353 /* 变量(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F94279EB96F00B61353 /* 变量(ap).md */; }; 08448F97279EB9B000B61353 /* 打印(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F96279EB9B000B61353 /* 打印(ap).md */; }; 08448F9A279EBA2900B61353 /* 可选(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448F99279EBA2900B61353 /* 可选(ap).md */; }; @@ -107,87 +65,11 @@ 08448FE8279EC84B00B61353 /* 恒等(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FE7279EC84B00B61353 /* 恒等(ap).md */; }; 08448FEA279EC86700B61353 /* 运算符(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FE9279EC86700B61353 /* 运算符(ap).md */; }; 08448FEC279EC8BE00B61353 /* 注释(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FEB279EC8BE00B61353 /* 注释(ap).md */; }; - 08448FEF279EC93D00B61353 /* Combine是什么(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FEE279EC93D00B61353 /* Combine是什么(ap).md */; }; - 08448FF1279EC96500B61353 /* Combine的资料(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FF0279EC96500B61353 /* Combine的资料(ap).md */; }; - 08448FF4279EC9C500B61353 /* publisher(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FF3279EC9C500B61353 /* publisher(ap).md */; }; - 08448FF6279EC9E800B61353 /* Just(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FF5279EC9E800B61353 /* Just(ap).md */; }; - 08448FF8279ECA2000B61353 /* PassthroughSubject(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FF7279ECA2000B61353 /* PassthroughSubject(ap).md */; }; - 08448FFA279ECA5300B61353 /* Empty(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FF9279ECA5300B61353 /* Empty(ap).md */; }; - 08448FFC279ECA7C00B61353 /* CurrentValueSubject(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FFB279ECA7C00B61353 /* CurrentValueSubject(ap).md */; }; - 08448FFE279ECAA100B61353 /* removeDuplicates(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FFD279ECAA100B61353 /* removeDuplicates(ap).md */; }; - 08449000279ECAE100B61353 /* flatMap(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08448FFF279ECAE000B61353 /* flatMap(ap).md */; }; - 08449002279ECB0500B61353 /* append(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449001279ECB0500B61353 /* append(ap).md */; }; - 08449004279ECB2900B61353 /* prepend(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449003279ECB2900B61353 /* prepend(ap).md */; }; - 08449006279ECB4900B61353 /* merge(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449005279ECB4900B61353 /* merge(ap).md */; }; - 08449008279ECB6500B61353 /* zip(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449007279ECB6500B61353 /* zip(ap).md */; }; - 0844900A279ECB8C00B61353 /* combineLatest(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449009279ECB8C00B61353 /* combineLatest(ap).md */; }; - 0844900C279ECBB400B61353 /* Scheduler(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0844900B279ECBB400B61353 /* Scheduler(ap).md */; }; - 0844900F279ECC0C00B61353 /* Combine网络请求(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0844900E279ECC0C00B61353 /* Combine网络请求(ap).md */; }; - 08449011279ECC3E00B61353 /* Combine KVO(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449010279ECC3E00B61353 /* Combine KVO(ap).md */; }; - 08449013279ECC6300B61353 /* Combine通知(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449012279ECC6300B61353 /* Combine通知(ap).md */; }; - 08449015279ECC8300B61353 /* Combine Timer(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449014279ECC8300B61353 /* Combine Timer(ap).md */; }; - 08449018279ECD2400B61353 /* Swift Concurrency是什么(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449017279ECD2400B61353 /* Swift Concurrency是什么(ap).md */; }; - 0844901A279ECD4E00B61353 /* async await(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449019279ECD4E00B61353 /* async await(ap).md */; }; - 0844901C279ECD6B00B61353 /* Async Sequences(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0844901B279ECD6B00B61353 /* Async Sequences(ap).md */; }; - 0844901E279ECD9D00B61353 /* 结构化并发(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0844901D279ECD9D00B61353 /* 结构化并发(ap).md */; }; - 08449020279ECDCE00B61353 /* Actors(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0844901F279ECDCE00B61353 /* Actors(ap).md */; }; - 08449022279ECDEF00B61353 /* Swift Concurrency相关提案(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449021279ECDEF00B61353 /* Swift Concurrency相关提案(ap).md */; }; - 08449024279ECE2200B61353 /* Swift Concurrency学习路径(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449023279ECE2200B61353 /* Swift Concurrency学习路径(ap).md */; }; - 08449026279ECE5400B61353 /* Swift Concurrency和Combine(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449025279ECE5400B61353 /* Swift Concurrency和Combine(ap).md */; }; - 08449029279ECEB100B61353 /* SwiftUI是什么(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08449028279ECEB100B61353 /* SwiftUI是什么(ap).md */; }; - 0844902C279ECEFB00B61353 /* SwiftUI对标的UIKit视图(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0844902B279ECEFB00B61353 /* SwiftUI对标的UIKit视图(ap).md */; }; - 0844902E279ECF1C00B61353 /* Text(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0844902D279ECF1C00B61353 /* Text(ap).md */; }; 08449030279ECF7D00B61353 /* 1.md in Resources */ = {isa = PBXBuildFile; fileRef = 0844902F279ECF7D00B61353 /* 1.md */; }; - 084E1A5F27B4F7BB0072BBB6 /* @dynamicCallable动态可调用类型(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 084E1A5E27B4F7BB0072BBB6 /* @dynamicCallable动态可调用类型(ap).md */; }; 084E1A6327B517FC0072BBB6 /* Swift各版本演进(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 084E1A6227B517FC0072BBB6 /* Swift各版本演进(ap).md */; }; 084E1A6527B51EDB0072BBB6 /* AutoTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084E1A6427B51EDB0072BBB6 /* AutoTask.swift */; }; - 0850444C27B0D1F80096D556 /* canImport判断库是否可使用(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850444B27B0D1F80096D556 /* canImport判断库是否可使用(ap).md */; }; - 0850444E27B0D4EA0096D556 /* targetEnvironment环境的判断(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850444D27B0D4EA0096D556 /* targetEnvironment环境的判断(ap).md */; }; - 0850445027B0FDBD0096D556 /* @resultBuilder(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850444F27B0FDBD0096D556 /* @resultBuilder(ap).md */; }; - 0850445627B110DA0096D556 /* Hashable(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850445527B110DA0096D556 /* Hashable(ap).md */; }; 0850445827B1228E0096D556 /* Result(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850445727B1228E0096D556 /* Result(ap).md */; }; - 0850AC062BF2E5DA009FDBBF /* List-移动元素(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC052BF2E5DA009FDBBF /* List-移动元素(ap).md */; }; - 0850AC082BF2E63C009FDBBF /* List-搜索(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC072BF2E63C009FDBBF /* List-搜索(ap).md */; }; - 0850AC0A2BF2F8AE009FDBBF /* List-下拉刷新(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC092BF2F8AE009FDBBF /* List-下拉刷新(ap).md */; }; - 0850AC0C2BF2FA2F009FDBBF /* List-轻扫操作(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC0B2BF2FA2F009FDBBF /* List-轻扫操作(ap).md */; }; - 0850AC0E2BF2FB62009FDBBF /* List-大纲视图(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC0D2BF2FB62009FDBBF /* List-大纲视图(ap).md */; }; - 0850AC102BF30058009FDBBF /* List-完全可点击的行(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC0F2BF30058009FDBBF /* List-完全可点击的行(ap).md */; }; - 0850AC122BF3036D009FDBBF /* List-设置样式(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC112BF3036D009FDBBF /* List-设置样式(ap).md */; }; - 0850AC142BF34D8B009FDBBF /* List-索引标题(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC132BF34D8B009FDBBF /* List-索引标题(ap).md */; }; - 0850AC162BF35239009FDBBF /* List-加载更多(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC152BF35239009FDBBF /* List-加载更多(ap).md */; }; - 0850AC192BF35A26009FDBBF /* Grid(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC182BF35A26009FDBBF /* Grid(ap).md */; }; - 0850AC1C2BF3A333009FDBBF /* Table-样式(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC1B2BF3A333009FDBBF /* Table-样式(ap).md */; }; - 0850AC1E2BF3B340009FDBBF /* Table-行的选择(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC1D2BF3B340009FDBBF /* Table-行的选择(ap).md */; }; - 0850AC202BF3B572009FDBBF /* Table-多属性排序(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC1F2BF3B572009FDBBF /* Table-多属性排序(ap).md */; }; - 0850AC222BF41F65009FDBBF /* Table-contextMenu(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0850AC212BF41F65009FDBBF /* Table-contextMenu(ap).md */; }; - 08522BD627CF3218005FF059 /* Picker(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08522BD527CF3218005FF059 /* Picker(ap).md */; }; - 08522BDA27CF5029005FF059 /* Slider(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08522BD927CF5029005FF059 /* Slider(ap).md */; }; - 08522BDE27CF5133005FF059 /* Stepper(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08522BDD27CF5133005FF059 /* Stepper(ap).md */; }; - 08522BE427CF5C55005FF059 /* SwiftUI颜色(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08522BE327CF5C55005FF059 /* SwiftUI颜色(ap).md */; }; - 08522BE927CF6E3B005FF059 /* SwiftUI Effect(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08522BE827CF6E3A005FF059 /* SwiftUI Effect(ap).md */; }; - 08522BED27CF7A0C005FF059 /* Keyboard(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08522BEC27CF7A0C005FF059 /* Keyboard(ap).md */; }; - 0858C5C72BEBD230004F4C04 /* ContentUnavailableView(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0858C5C62BEBD230004F4C04 /* ContentUnavailableView(ap).md */; }; - 0858C5C92BECCD17004F4C04 /* SwiftData-资料(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0858C5C82BECCD17004F4C04 /* SwiftData-资料(ap).md */; }; - 085BB77427D22FCA00E8F69A /* SwiftUI动画(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 085BB77327D22FCA00E8F69A /* SwiftUI动画(ap).md */; }; - 085BB77627D22FE300E8F69A /* SwiftUI Canvas(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 085BB77527D22FE300E8F69A /* SwiftUI Canvas(ap).md */; }; - 085F06A12BF73AB80090310F /* Sheet(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 085F06A02BF73AB80090310F /* Sheet(ap).md */; }; - 08659BC72BE8FD84009B7C00 /* SwiftUI数据流(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BC62BE8FD84009B7C00 /* SwiftUI数据流(ap).md */; }; - 08659BCD2BE9A40A009B7C00 /* 创建@Model模型(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BCC2BE9A40A009B7C00 /* 创建@Model模型(ap).md */; }; - 08659BCF2BE9A430009B7C00 /* 容器配置modelContainer(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BCE2BE9A430009B7C00 /* 容器配置modelContainer(ap).md */; }; - 08659BD12BE9A448009B7C00 /* 增删modelContext(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BD02BE9A448009B7C00 /* 增删modelContext(ap).md */; }; - 08659BD32BE9A478009B7C00 /* SwiftData-检索(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BD22BE9A478009B7C00 /* SwiftData-检索(ap).md */; }; - 08659BD72BE9A7F8009B7C00 /* SwiftData-处理大量数据(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BD62BE9A7F8009B7C00 /* SwiftData-处理大量数据(ap).md */; }; - 08659BD92BE9A80E009B7C00 /* SwiftData多线程(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BD82BE9A80E009B7C00 /* SwiftData多线程(ap).md */; }; - 08659BDB2BE9A834009B7C00 /* SwiftData-版本迁移(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BDA2BE9A834009B7C00 /* SwiftData-版本迁移(ap).md */; }; - 08659BDD2BE9E3AA009B7C00 /* SwiftData-调试(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BDC2BE9E3AA009B7C00 /* SwiftData-调试(ap).md */; }; - 08659BDF2BEA4D8C009B7C00 /* SwiftData-模型关系(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08659BDE2BEA4D8C009B7C00 /* SwiftData-模型关系(ap).md */; }; 0868D00B2BDD37280023C871 /* SMGitHub in Frameworks */ = {isa = PBXBuildFile; productRef = 0868D00A2BDD37280023C871 /* SMGitHub */; }; - 086923312BF171A6006779A3 /* ForEach(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086923302BF171A6006779A3 /* ForEach(ap).md */; }; - 086923342BF178D9006779A3 /* 固定到滚动视图的顶部(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086923332BF178D9006779A3 /* 固定到滚动视图的顶部(ap).md */; }; - 086923362BF18918006779A3 /* 滚动到特定的位置(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086923352BF18918006779A3 /* 滚动到特定的位置(ap).md */; }; - 086923382BF19AB7006779A3 /* scrollTargetBehavior分页滚动(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086923372BF19AB7006779A3 /* scrollTargetBehavior分页滚动(ap).md */; }; - 0869233A2BF1A490006779A3 /* scrollTransition视觉效果(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086923392BF1A490006779A3 /* scrollTransition视觉效果(ap).md */; }; - 0869233C2BF1BF35006779A3 /* ScrollView-参考资料(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0869233B2BF1BF35006779A3 /* ScrollView-参考资料(ap).md */; }; 086A5F072744E88E00FECE02 /* SwiftPamphletAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086A5F062744E88E00FECE02 /* SwiftPamphletAppApp.swift */; }; 086A5F0B2744E89100FECE02 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 086A5F0A2744E89100FECE02 /* Assets.xcassets */; }; 086A5F0E2744E89100FECE02 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 086A5F0D2744E89100FECE02 /* Preview Assets.xcassets */; }; @@ -196,129 +78,26 @@ 086A5F442744EE2800FECE02 /* SwiftPamphletAppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086A5F432744EE2800FECE02 /* SwiftPamphletAppConfig.swift */; }; 086A5F462744EEB900FECE02 /* FundationFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086A5F452744EEB900FECE02 /* FundationFunction.swift */; }; 086A5F522744EF4C00FECE02 /* ViewComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086A5F512744EF4C00FECE02 /* ViewComponent.swift */; }; - 086BEEF82BF629DB00025307 /* AnyLayout(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEF72BF629DB00025307 /* AnyLayout(ap).md */; }; - 086BEEFA2BF6300400025307 /* ViewThatFits(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEF92BF6300400025307 /* ViewThatFits(ap).md */; }; - 086BEEFC2BF63A0000025307 /* Layout协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEFB2BF63A0000025307 /* Layout协议(ap).md */; }; - 086BEEFE2BF644D400025307 /* GeometryReader(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEFD2BF644D400025307 /* GeometryReader(ap).md */; }; - 086BEF002BF659E500025307 /* alignmentGuide(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEFF2BF659E500025307 /* alignmentGuide(ap).md */; }; - 086BEF022BF65DCD00025307 /* 布局进阶-参考资料(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEF012BF65DCD00025307 /* 布局进阶-参考资料(ap).md */; }; - 086BEF052BF6C38300025307 /* 文字Picker(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEF042BF6C38300025307 /* 文字Picker(ap).md */; }; - 086BEF072BF6C39B00025307 /* ColorPicker(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEF062BF6C39B00025307 /* ColorPicker(ap).md */; }; - 086BEF092BF6C3B100025307 /* DatePicker(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEF082BF6C3B100025307 /* DatePicker(ap).md */; }; - 086BEF0B2BF6C40300025307 /* PhotoPicker(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEF0A2BF6C40300025307 /* PhotoPicker(ap).md */; }; - 086BEF0D2BF6C42500025307 /* 字体Picker(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEF0C2BF6C42500025307 /* 字体Picker(ap).md */; }; - 086BEF0F2BF6C43800025307 /* WheelPicker(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEF0E2BF6C43800025307 /* WheelPicker(ap).md */; }; 0871C6192BA040E5000B620D /* InfoRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0871C6182BA040E5000B620D /* InfoRowView.swift */; }; 0871C61B2BA04D23000B620D /* CategoryRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0871C61A2BA04D23000B620D /* CategoryRowView.swift */; }; 0871C61D2BA05F44000B620D /* InfosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0871C61C2BA05F44000B620D /* InfosView.swift */; }; - 087ECDFE2BFCC3560011F679 /* Full Screen Modal View(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECDFD2BFCC3560011F679 /* Full Screen Modal View(ap).md */; }; - 087ECE002BFCC36F0011F679 /* confirmationDialog()(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECDFF2BFCC36F0011F679 /* confirmationDialog()(ap).md */; }; - 087ECE022BFCC3980011F679 /* Alert(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE012BFCC3980011F679 /* Alert(ap).md */; }; - 087ECE042BFCC3AA0011F679 /* Popover(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE032BFCC3AA0011F679 /* Popover(ap).md */; }; - 087ECE062BFCC3BD0011F679 /* Menu和ContextMenu(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE052BFCC3BD0011F679 /* Menu和ContextMenu(ap).md */; }; - 087ECE082BFCC3D90011F679 /* HUD(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE072BFCC3D90011F679 /* HUD(ap).md */; }; - 087ECE0E2BFDE0630011F679 /* 自定义修饰符(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE0D2BFDE0630011F679 /* 自定义修饰符(ap).md */; }; - 087ECE102BFDEA230011F679 /* 背景修饰符(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE0F2BFDEA230011F679 /* 背景修饰符(ap).md */; }; - 087ECE122BFDF07A0011F679 /* 修饰符-visualEffect(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE112BFDF07A0011F679 /* 修饰符-visualEffect(ap).md */; }; - 087ECE142BFDF7530011F679 /* 修饰符-圆角(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE132BFDF7530011F679 /* 修饰符-圆角(ap).md */; }; - 087ECE162BFE01F80011F679 /* ContainerRelativeShape(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE152BFE01F80011F679 /* ContainerRelativeShape(ap).md */; }; - 087ECE182BFE0E000011F679 /* 修饰符-fixedSize(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE172BFE0E000011F679 /* 修饰符-fixedSize(ap).md */; }; - 087ECE1A2BFEA8210011F679 /* 修饰符-蒙版(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE192BFEA8210011F679 /* 修饰符-蒙版(ap).md */; }; - 087ECE1C2BFEBF390011F679 /* redacted隐私展示(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE1B2BFEBF390011F679 /* redacted隐私展示(ap).md */; }; - 087ECE1E2BFEC2090011F679 /* SwiftUI-自定义样式(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE1D2BFEC2090011F679 /* SwiftUI-自定义样式(ap).md */; }; - 087ECE202BFEC21B0011F679 /* ViewBuilder(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE1F2BFEC21B0011F679 /* ViewBuilder(ap).md */; }; - 087ECE222BFED0390011F679 /* 视图协议-简介(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE212BFED0390011F679 /* 视图协议-简介(ap).md */; }; - 087ECE252BFF25940011F679 /* 视图协议-核心协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE242BFF25940011F679 /* 视图协议-核心协议(ap).md */; }; - 087ECE272BFF25AC0011F679 /* Style协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE262BFF25AC0011F679 /* Style协议(ap).md */; }; - 087ECE292BFF25C00011F679 /* 小组件协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE282BFF25C00011F679 /* 小组件协议(ap).md */; }; - 087ECE2B2BFF25D30011F679 /* Shapes协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE2A2BFF25D30011F679 /* Shapes协议(ap).md */; }; - 087ECE2D2BFF25E70011F679 /* Animations协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE2C2BFF25E70011F679 /* Animations协议(ap).md */; }; - 087ECE2F2BFF26000011F679 /* 视图协议-Environment(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE2E2BFF26000011F679 /* 视图协议-Environment(ap).md */; }; - 087ECE312BFF261C0011F679 /* Previews协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE302BFF261C0011F679 /* Previews协议(ap).md */; }; - 087ECE332BFF264D0011F679 /* Legacy bridges协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE322BFF264D0011F679 /* Legacy bridges协议(ap).md */; }; - 087ECE352BFF26620011F679 /* Responder chain协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE342BFF26620011F679 /* Responder chain协议(ap).md */; }; - 087ECE372BFF26790011F679 /* Toolbar协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE362BFF26790011F679 /* Toolbar协议(ap).md */; }; - 087ECE392BFF26910011F679 /* Documents协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE382BFF26910011F679 /* Documents协议(ap).md */; }; - 087ECE3B2BFF26B00011F679 /* 特定情况视图协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE3A2BFF26B00011F679 /* 特定情况视图协议(ap).md */; }; - 087ECE3D2BFF491B0011F679 /* SwiftUI-Shadow(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE3C2BFF491B0011F679 /* SwiftUI-Shadow(ap).md */; }; - 087ECE3F2BFF8A690011F679 /* Blend Modes(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE3E2BFF8A690011F679 /* Blend Modes(ap).md */; }; - 087ECE412C00532F0011F679 /* SwiftUI-渐变(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE402C00532F0011F679 /* SwiftUI-渐变(ap).md */; }; - 087ECE432C0066740011F679 /* SwiftUI-模糊(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE422C0066740011F679 /* SwiftUI-模糊(ap).md */; }; - 087ECE452C006A170011F679 /* SwiftUI-背景材质(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE442C006A170011F679 /* SwiftUI-背景材质(ap).md */; }; - 087ECE472C00756C0011F679 /* AppIcon(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE462C00756C0011F679 /* AppIcon(ap).md */; }; - 087ECE492C00DC1E0011F679 /* contentTransition(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE482C00DC1E0011F679 /* contentTransition(ap).md */; }; - 087ECE4B2C01768B0011F679 /* animation修饰符(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE4A2C01768B0011F679 /* animation修饰符(ap).md */; }; - 087ECE4D2C018E7B0011F679 /* Transaction(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE4C2C018E7B0011F679 /* Transaction(ap).md */; }; - 087ECE4F2C01B7280011F679 /* Matched Geometry Effect(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE4E2C01B7280011F679 /* Matched Geometry Effect(ap).md */; }; - 087ECE512C01D6E10011F679 /* PhaseAnimator(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE502C01D6E10011F679 /* PhaseAnimator(ap).md */; }; - 087ECE532C01E5B80011F679 /* KeyframeAnimator(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE522C01E5B80011F679 /* KeyframeAnimator(ap).md */; }; - 087ECE552C01F0A10011F679 /* Shaders Metal(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE542C01F0A10011F679 /* Shaders Metal(ap).md */; }; - 087ECE572C01F3A20011F679 /* 动画-例子(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 087ECE562C01F3A20011F679 /* 动画-例子(ap).md */; }; 0887A59A2BA28F6D00131359 /* CSGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0887A5992BA28F6D00131359 /* CSGuideView.swift */; }; 0888EF752C1C280F004967AD /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0869233F2BF2BF81006779A3 /* AVKit.framework */; }; - 0896FB9227BA486900676B7F /* Button(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 0896FB9127BA486900676B7F /* Button(ap).md */; }; - 08A4FDC227B25A140068E5BC /* @dynamicMemberLookup动态成员查询(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08A4FDC127B25A140068E5BC /* @dynamicMemberLookup动态成员查询(ap).md */; }; 08A7FF312BEB02EA00E12E5A /* GithubAccessTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A7FF302BEB02EA00E12E5A /* GithubAccessTokenView.swift */; }; 08A9E1A22BC25D0700A73764 /* ViewComponentMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A9E1A12BC25D0700A73764 /* ViewComponentMarkdown.swift */; }; 08AEAEF1277F09D000B969E2 /* IntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AEAEF0277F09D000B969E2 /* IntroView.swift */; }; 08AEAEFA277F3C7400B969E2 /* css.html in Resources */ = {isa = PBXBuildFile; fileRef = 08AEAEF9277F3C7400B969E2 /* css.html */; }; - 08B6B1012BF4FFE8007B6E2D /* 布局-居中(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08B6B1002BF4FFE8007B6E2D /* 布局-居中(ap).md */; }; - 08B6B1032BF50154007B6E2D /* 布局-offset偏移(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08B6B1022BF50154007B6E2D /* 布局-offset偏移(ap).md */; }; - 08B6B1052BF5059D007B6E2D /* 布局原理(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08B6B1042BF5059D007B6E2D /* 布局原理(ap).md */; }; - 08B6B1072BF598E8007B6E2D /* Safe Area(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08B6B1062BF598E8007B6E2D /* Safe Area(ap).md */; }; - 08BE632A27BE220D002BC6A8 /* 全屏模式(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE632927BE220D002BC6A8 /* 全屏模式(ap).md */; }; - 08BE632C27BE3762002BC6A8 /* Link(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE632B27BE3762002BC6A8 /* Link(ap).md */; }; - 08BE633027BE6CAA002BC6A8 /* Label(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE632F27BE6CAA002BC6A8 /* Label(ap).md */; }; - 08BE634227BFAF76002BC6A8 /* TextEditor(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE634127BFAF76002BC6A8 /* TextEditor(ap).md */; }; - 08BE634427C3845E002BC6A8 /* TextField(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE634327C3845E002BC6A8 /* TextField(ap).md */; }; - 08BE634A27C4BDDB002BC6A8 /* Stack(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE634927C4BDDB002BC6A8 /* Stack(ap).md */; }; - 08BE635427C63828002BC6A8 /* List(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE635327C63828002BC6A8 /* List(ap).md */; }; - 08BE635827C63F3A002BC6A8 /* ControlGroup(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE635727C63F3A002BC6A8 /* ControlGroup(ap).md */; }; - 08BE635C27C65C7C002BC6A8 /* GroupBox(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE635B27C65C7C002BC6A8 /* GroupBox(ap).md */; }; - 08BE636427C886D2002BC6A8 /* LazyVStack和LazyHStack(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE636327C886D2002BC6A8 /* LazyVStack和LazyHStack(ap).md */; }; - 08BE636827C8C2A0002BC6A8 /* 进度(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE636727C8C2A0002BC6A8 /* 进度(ap).md */; }; - 08BE636C27C8CFA7002BC6A8 /* Image(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE636B27C8CFA7002BC6A8 /* Image(ap).md */; }; - 08BE637027C8F6A7002BC6A8 /* LazyVGrid和LazyHGrid(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE636F27C8F6A7002BC6A8 /* LazyVGrid和LazyHGrid(ap).md */; }; - 08BE637427CCAB52002BC6A8 /* ScrollView(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE637327CCAB52002BC6A8 /* ScrollView(ap).md */; }; - 08BE638F27CE157D002BC6A8 /* 浮层(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08BE638E27CE157D002BC6A8 /* 浮层(ap).md */; }; 08BF26D32768A5B40064DDAC /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 08BF26D22768A5B40064DDAC /* MarkdownUI */; }; - 08C3BB8027CE4A8500ACF0FE /* TabView(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08C3BB7F27CE4A8500ACF0FE /* TabView(ap).md */; }; - 08C3BBA227CF1B2B00ACF0FE /* Toggle(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08C3BBA127CF1B2B00ACF0FE /* Toggle(ap).md */; }; 08CD61FE27758B8A008C0935 /* Lexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CD61FC27758B8A008C0935 /* Lexer.swift */; }; 08CD61FF27758B8A008C0935 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CD61FD27758B8A008C0935 /* Token.swift */; }; 08D107BD278826BB007B7009 /* HTMLEntities in Frameworks */ = {isa = PBXBuildFile; productRef = 08D107BC278826BB007B7009 /* HTMLEntities */; }; - 08D4EBD32BF437510031EDC5 /* Navigation(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBD22BF437510031EDC5 /* Navigation(ap).md */; }; - 08D4EBD52BF4379E0031EDC5 /* NavigationStack(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBD42BF4379E0031EDC5 /* NavigationStack(ap).md */; }; - 08D4EBD72BF43C540031EDC5 /* NavigationPath(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBD62BF43C540031EDC5 /* NavigationPath(ap).md */; }; - 08D4EBD92BF44C170031EDC5 /* NavigationSplitView(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBD82BF44C170031EDC5 /* NavigationSplitView(ap).md */; }; - 08D4EBDB2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBDA2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md */; }; - 08D4EBDD2BF461F60031EDC5 /* 自定义导航栏(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBDC2BF461F60031EDC5 /* 自定义导航栏(ap).md */; }; - 08D4EBDF2BF497030031EDC5 /* 导航状态保存和还原(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBDE2BF497030031EDC5 /* 导航状态保存和还原(ap).md */; }; - 08D4EBE22BF4A76A0031EDC5 /* 布局-基础(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBE12BF4A76A0031EDC5 /* 布局-基础(ap).md */; }; - 08D4EBE42BF4AB9A0031EDC5 /* 布局-留白(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBE32BF4AB9A0031EDC5 /* 布局-留白(ap).md */; }; - 08D4EBE62BF4B8050031EDC5 /* 布局-对齐(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBE52BF4B8050031EDC5 /* 布局-对齐(ap).md */; }; 08D8EFE52BED825E00AA0020 /* BookmarkListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D8EFE42BED825E00AA0020 /* BookmarkListView.swift */; }; - 08D8EFE92BEEF68800AA0020 /* 小组件-StaticConfiguration(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFE82BEEF68800AA0020 /* 小组件-StaticConfiguration(ap).md */; }; - 08D8EFEB2BEF106B00AA0020 /* 小组件-AppIntentConfiguration(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFEA2BEF106B00AA0020 /* 小组件-AppIntentConfiguration(ap).md */; }; - 08D8EFED2BEF3F2100AA0020 /* 小组件-配置选项(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFEC2BEF3F2100AA0020 /* 小组件-配置选项(ap).md */; }; - 08D8EFEF2BEF45CD00AA0020 /* AppIntentTimelineProvider(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFEE2BEF45CD00AA0020 /* AppIntentTimelineProvider(ap).md */; }; - 08D8EFF12BEF4E6900AA0020 /* Widget View(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFF02BEF4E6900AA0020 /* Widget View(ap).md */; }; - 08D8EFF32BEF78B400AA0020 /* 刷新小组件(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFF22BEF78B400AA0020 /* 刷新小组件(ap).md */; }; - 08D8EFF62BEF8CAB00AA0020 /* Text-动态时间(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFF52BEF8CAB00AA0020 /* Text-动态时间(ap).md */; }; - 08D8EFF82BEF912700AA0020 /* 小组件动画(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFF72BEF912700AA0020 /* 小组件动画(ap).md */; }; - 08D8EFFA2BEF9C9800AA0020 /* 小组件-远程定时获取数据(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFF92BEF9C9800AA0020 /* 小组件-远程定时获取数据(ap).md */; }; - 08D8EFFC2BEF9EDE00AA0020 /* 小组件-获取位置权限更新内容(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFFB2BEF9EDE00AA0020 /* 小组件-获取位置权限更新内容(ap).md */; }; - 08D8EFFE2BEFA3E300AA0020 /* 支持多个小组件(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFFD2BEFA3E300AA0020 /* 支持多个小组件(ap).md */; }; - 08D8F0002BEFA72300AA0020 /* 获取小组件形状(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFFF2BEFA72300AA0020 /* 获取小组件形状(ap).md */; }; - 08D8F0022BEFA84E00AA0020 /* 小组件-Deep link(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8F0012BEFA84E00AA0020 /* 小组件-Deep link(ap).md */; }; - 08D8F0042BEFA86C00AA0020 /* 小组件-参考资料(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8F0032BEFA86C00AA0020 /* 小组件-参考资料(ap).md */; }; 08D8F0072BEFBAC700AA0020 /* WWDCData.json in Resources */ = {isa = PBXBuildFile; fileRef = 08D8F0062BEFBAC700AA0020 /* WWDCData.json */; }; 08D8F00A2BEFBB2300AA0020 /* WWDCModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D8F0092BEFBB2300AA0020 /* WWDCModel.swift */; }; 08D8F00C2BEFCFCF00AA0020 /* WWDCListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D8F00B2BEFCFCF00AA0020 /* WWDCListView.swift */; }; 08D8F00E2BF044FB00AA0020 /* WWDCDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D8F00D2BF044FB00AA0020 /* WWDCDetailView.swift */; }; 08ED80162B9C54DE0069B7EC /* SMNetwork in Frameworks */ = {isa = PBXBuildFile; productRef = 08ED80152B9C54DE0069B7EC /* SMNetwork */; }; 08ED801C2B9D1EEC0069B7EC /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08ED801B2B9D1EEC0069B7EC /* SettingView.swift */; }; - 08EF35CD2BECF3120098E2D4 /* 小组件访问SwiftData(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08EF35CC2BECF3120098E2D4 /* 小组件访问SwiftData(ap).md */; }; 08EF35D22BECFDA80098E2D4 /* BookmarkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08EF35D12BECFDA80098E2D4 /* BookmarkModel.swift */; }; 08F14B3C2BBDA3EA005B46CC /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = 08F14B3B2BBDA3EA005B46CC /* Nuke */; }; 08F14B3E2BBDA3EA005B46CC /* NukeExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 08F14B3D2BBDA3EA005B46CC /* NukeExtensions */; }; @@ -341,29 +120,9 @@ /* Begin PBXFileReference section */ 080124FD27EC62DC00E44222 /* SwiftPamphletAppDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftPamphletAppDebug.entitlements; sourceTree = ""; }; - 08026C2F2869945300792EF1 /* Concurrency技术演进(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Concurrency技术演进(ap).md"; sourceTree = ""; }; - 08026C302869948400792EF1 /* Distributed Actors(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Distributed Actors(ap).md"; sourceTree = ""; }; - 08026C322869991200792EF1 /* Swift-DocC(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Swift-DocC(ap).md"; sourceTree = ""; }; - 08026C34286999BB00792EF1 /* 调试(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "调试(ap).md"; sourceTree = ""; }; - 08026C3528699AAA00792EF1 /* 内存管理(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "内存管理(ap).md"; sourceTree = ""; }; - 08026C3628699C8700792EF1 /* 链接器(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "链接器(ap).md"; sourceTree = ""; }; - 08026C3828699EB700792EF1 /* SwiftUI参考资料(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI参考资料(ap).md"; sourceTree = ""; }; - 08026C392869A17800792EF1 /* SwiftCharts(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftCharts(ap).md"; sourceTree = ""; }; - 08026C3A2869A21000792EF1 /* Advanced layout control(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Advanced layout control(ap).md"; sourceTree = ""; }; - 08026C3B2869A2F100792EF1 /* Transferable(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Transferable(ap).md"; sourceTree = ""; }; - 08026C3C2869A35800792EF1 /* ShareLink(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "ShareLink(ap).md"; sourceTree = ""; }; - 08026C3D2869A51200792EF1 /* Form(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Form(ap).md"; sourceTree = ""; }; - 08026C3E2869AD7800792EF1 /* SF Symbol(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SF Symbol(ap).md"; sourceTree = ""; }; - 08026C3F2869B00D00792EF1 /* macOS技术演进(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "macOS技术演进(ap).md"; sourceTree = ""; }; - 08026C402869B1BF00792EF1 /* 性能技术演进(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "性能技术演进(ap).md"; sourceTree = ""; }; 08026C422869B22E00792EF1 /* Regex(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Regex(ap).md"; sourceTree = ""; }; - 08026C502869B41B00792EF1 /* Table(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Table(ap).md"; sourceTree = ""; }; 08069CAA2BDE01E800D48E24 /* GuideListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideListView.swift; sourceTree = ""; }; 08069CAC2BDE7A6B00D48E24 /* GuideDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideDetailView.swift; sourceTree = ""; }; - 0820367F2C073C0E002FB5E3 /* 格式化-时间(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "格式化-时间(ap).md"; sourceTree = ""; }; - 082036812C073C24002FB5E3 /* 格式化-数据(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "格式化-数据(ap).md"; sourceTree = ""; }; - 082036832C075A01002FB5E3 /* 格式化-度量值(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "格式化-度量值(ap).md"; sourceTree = ""; }; - 082036852C075A18002FB5E3 /* 格式化-生活日常(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "格式化-生活日常(ap).md"; sourceTree = ""; }; 0825E4862BC6596F00332378 /* EditCustomSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCustomSearchView.swift; sourceTree = ""; }; 08397E242B9EEE1300DFDD02 /* InfoListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoListView.swift; sourceTree = ""; }; 08397E282B9F0A9100DFDD02 /* EditInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditInfoView.swift; sourceTree = ""; }; @@ -373,30 +132,8 @@ 084417762B99BA3F0049297D /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; 084417782B99BE720049297D /* DataLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataLink.swift; sourceTree = ""; }; 08448F0E2799328700B61353 /* css_cn.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = css_cn.html; sourceTree = ""; }; - 08448F50279E900500B61353 /* macOS剪贴板(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "macOS剪贴板(ap).md"; sourceTree = ""; }; - 08448F56279EA84100B61353 /* macOS范例(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "macOS范例(ap).md"; sourceTree = ""; }; - 08448F57279EA84100B61353 /* 三栏结构(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "三栏结构(ap).md"; sourceTree = ""; }; - 08448F58279EA84100B61353 /* macOS共享菜单(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "macOS共享菜单(ap).md"; sourceTree = ""; }; - 08448F5D279EB23600B61353 /* SQLite.swift的使用(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SQLite.swift的使用(ap).md"; sourceTree = ""; }; 08448F63279EB32C00B61353 /* Swift规范(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Swift规范(ap).md"; sourceTree = ""; }; 08448F65279EB33F00B61353 /* Swift书单(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Swift书单(ap).md"; sourceTree = ""; }; - 08448F69279EB47E00B61353 /* 时间(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "时间(ap).md"; sourceTree = ""; }; - 08448F6C279EB51000B61353 /* 格式化(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "格式化(ap).md"; sourceTree = ""; }; - 08448F6E279EB56400B61353 /* 度量值(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "度量值(ap).md"; sourceTree = ""; }; - 08448F70279EB58C00B61353 /* Data(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Data(ap).md"; sourceTree = ""; }; - 08448F72279EB5DF00B61353 /* 文件(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "文件(ap).md"; sourceTree = ""; }; - 08448F74279EB62B00B61353 /* Scanner(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Scanner(ap).md"; sourceTree = ""; }; - 08448F76279EB65800B61353 /* AttributeString(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "AttributeString(ap).md"; sourceTree = ""; }; - 08448F78279EB68D00B61353 /* 随机(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "随机(ap).md"; sourceTree = ""; }; - 08448F7A279EB6CC00B61353 /* UserDefaults(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "UserDefaults(ap).md"; sourceTree = ""; }; - 08448F7D279EB71D00B61353 /* 单例(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "单例(ap).md"; sourceTree = ""; }; - 08448F80279EB75800B61353 /* 系统判断(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "系统判断(ap).md"; sourceTree = ""; }; - 08448F82279EB78A00B61353 /* 版本兼容(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "版本兼容(ap).md"; sourceTree = ""; }; - 08448F85279EB7D200B61353 /* JSON没有id字段(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "JSON没有id字段(ap).md"; sourceTree = ""; }; - 08448F88279EB7FD00B61353 /* 网络状态检查(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "网络状态检查(ap).md"; sourceTree = ""; }; - 08448F8B279EB84800B61353 /* 布局动画(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局动画(ap).md"; sourceTree = ""; }; - 08448F8E279EB88500B61353 /* Keychain(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Keychain(ap).md"; sourceTree = ""; }; - 08448F91279EB8CA00B61353 /* 程序入口点(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "程序入口点(ap).md"; sourceTree = ""; }; 08448F94279EB96F00B61353 /* 变量(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "变量(ap).md"; sourceTree = ""; }; 08448F96279EB9B000B61353 /* 打印(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "打印(ap).md"; sourceTree = ""; }; 08448F99279EBA2900B61353 /* 可选(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "可选(ap).md"; sourceTree = ""; }; @@ -438,86 +175,10 @@ 08448FE7279EC84B00B61353 /* 恒等(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "恒等(ap).md"; sourceTree = ""; }; 08448FE9279EC86700B61353 /* 运算符(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "运算符(ap).md"; sourceTree = ""; }; 08448FEB279EC8BE00B61353 /* 注释(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "注释(ap).md"; sourceTree = ""; }; - 08448FEE279EC93D00B61353 /* Combine是什么(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Combine是什么(ap).md"; sourceTree = ""; }; - 08448FF0279EC96500B61353 /* Combine的资料(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Combine的资料(ap).md"; sourceTree = ""; }; - 08448FF3279EC9C500B61353 /* publisher(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "publisher(ap).md"; sourceTree = ""; }; - 08448FF5279EC9E800B61353 /* Just(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Just(ap).md"; sourceTree = ""; }; - 08448FF7279ECA2000B61353 /* PassthroughSubject(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "PassthroughSubject(ap).md"; sourceTree = ""; }; - 08448FF9279ECA5300B61353 /* Empty(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Empty(ap).md"; sourceTree = ""; }; - 08448FFB279ECA7C00B61353 /* CurrentValueSubject(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "CurrentValueSubject(ap).md"; sourceTree = ""; }; - 08448FFD279ECAA100B61353 /* removeDuplicates(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "removeDuplicates(ap).md"; sourceTree = ""; }; - 08448FFF279ECAE000B61353 /* flatMap(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "flatMap(ap).md"; sourceTree = ""; }; - 08449001279ECB0500B61353 /* append(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "append(ap).md"; sourceTree = ""; }; - 08449003279ECB2900B61353 /* prepend(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "prepend(ap).md"; sourceTree = ""; }; - 08449005279ECB4900B61353 /* merge(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "merge(ap).md"; sourceTree = ""; }; - 08449007279ECB6500B61353 /* zip(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "zip(ap).md"; sourceTree = ""; }; - 08449009279ECB8C00B61353 /* combineLatest(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "combineLatest(ap).md"; sourceTree = ""; }; - 0844900B279ECBB400B61353 /* Scheduler(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Scheduler(ap).md"; sourceTree = ""; }; - 0844900E279ECC0C00B61353 /* Combine网络请求(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Combine网络请求(ap).md"; sourceTree = ""; }; - 08449010279ECC3E00B61353 /* Combine KVO(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Combine KVO(ap).md"; sourceTree = ""; }; - 08449012279ECC6300B61353 /* Combine通知(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Combine通知(ap).md"; sourceTree = ""; }; - 08449014279ECC8300B61353 /* Combine Timer(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Combine Timer(ap).md"; sourceTree = ""; }; - 08449017279ECD2400B61353 /* Swift Concurrency是什么(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Swift Concurrency是什么(ap).md"; sourceTree = ""; }; - 08449019279ECD4E00B61353 /* async await(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "async await(ap).md"; sourceTree = ""; }; - 0844901B279ECD6B00B61353 /* Async Sequences(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Async Sequences(ap).md"; sourceTree = ""; }; - 0844901D279ECD9D00B61353 /* 结构化并发(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "结构化并发(ap).md"; sourceTree = ""; }; - 0844901F279ECDCE00B61353 /* Actors(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Actors(ap).md"; sourceTree = ""; }; - 08449021279ECDEF00B61353 /* Swift Concurrency相关提案(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Swift Concurrency相关提案(ap).md"; sourceTree = ""; }; - 08449023279ECE2200B61353 /* Swift Concurrency学习路径(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Swift Concurrency学习路径(ap).md"; sourceTree = ""; }; - 08449025279ECE5400B61353 /* Swift Concurrency和Combine(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Swift Concurrency和Combine(ap).md"; sourceTree = ""; }; - 08449028279ECEB100B61353 /* SwiftUI是什么(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI是什么(ap).md"; sourceTree = ""; }; - 0844902B279ECEFB00B61353 /* SwiftUI对标的UIKit视图(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI对标的UIKit视图(ap).md"; sourceTree = ""; }; - 0844902D279ECF1C00B61353 /* Text(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Text(ap).md"; sourceTree = ""; }; 0844902F279ECF7D00B61353 /* 1.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = 1.md; sourceTree = ""; }; - 084E1A5E27B4F7BB0072BBB6 /* @dynamicCallable动态可调用类型(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "@dynamicCallable动态可调用类型(ap).md"; sourceTree = ""; }; 084E1A6227B517FC0072BBB6 /* Swift各版本演进(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Swift各版本演进(ap).md"; sourceTree = ""; }; 084E1A6427B51EDB0072BBB6 /* AutoTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoTask.swift; sourceTree = ""; }; - 0850444B27B0D1F80096D556 /* canImport判断库是否可使用(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "canImport判断库是否可使用(ap).md"; sourceTree = ""; }; - 0850444D27B0D4EA0096D556 /* targetEnvironment环境的判断(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "targetEnvironment环境的判断(ap).md"; sourceTree = ""; }; - 0850444F27B0FDBD0096D556 /* @resultBuilder(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "@resultBuilder(ap).md"; sourceTree = ""; }; - 0850445527B110DA0096D556 /* Hashable(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Hashable(ap).md"; sourceTree = ""; }; 0850445727B1228E0096D556 /* Result(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Result(ap).md"; sourceTree = ""; }; - 0850AC052BF2E5DA009FDBBF /* List-移动元素(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "List-移动元素(ap).md"; sourceTree = ""; }; - 0850AC072BF2E63C009FDBBF /* List-搜索(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "List-搜索(ap).md"; sourceTree = ""; }; - 0850AC092BF2F8AE009FDBBF /* List-下拉刷新(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "List-下拉刷新(ap).md"; sourceTree = ""; }; - 0850AC0B2BF2FA2F009FDBBF /* List-轻扫操作(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "List-轻扫操作(ap).md"; sourceTree = ""; }; - 0850AC0D2BF2FB62009FDBBF /* List-大纲视图(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "List-大纲视图(ap).md"; sourceTree = ""; }; - 0850AC0F2BF30058009FDBBF /* List-完全可点击的行(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "List-完全可点击的行(ap).md"; sourceTree = ""; }; - 0850AC112BF3036D009FDBBF /* List-设置样式(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "List-设置样式(ap).md"; sourceTree = ""; }; - 0850AC132BF34D8B009FDBBF /* List-索引标题(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "List-索引标题(ap).md"; sourceTree = ""; }; - 0850AC152BF35239009FDBBF /* List-加载更多(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "List-加载更多(ap).md"; sourceTree = ""; }; - 0850AC182BF35A26009FDBBF /* Grid(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Grid(ap).md"; sourceTree = ""; }; - 0850AC1B2BF3A333009FDBBF /* Table-样式(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Table-样式(ap).md"; sourceTree = ""; }; - 0850AC1D2BF3B340009FDBBF /* Table-行的选择(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Table-行的选择(ap).md"; sourceTree = ""; }; - 0850AC1F2BF3B572009FDBBF /* Table-多属性排序(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Table-多属性排序(ap).md"; sourceTree = ""; }; - 0850AC212BF41F65009FDBBF /* Table-contextMenu(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Table-contextMenu(ap).md"; sourceTree = ""; }; - 08522BD527CF3218005FF059 /* Picker(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Picker(ap).md"; sourceTree = ""; }; - 08522BD927CF5029005FF059 /* Slider(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Slider(ap).md"; sourceTree = ""; }; - 08522BDD27CF5133005FF059 /* Stepper(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Stepper(ap).md"; sourceTree = ""; }; - 08522BE327CF5C55005FF059 /* SwiftUI颜色(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI颜色(ap).md"; sourceTree = ""; }; - 08522BE827CF6E3A005FF059 /* SwiftUI Effect(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI Effect(ap).md"; sourceTree = ""; }; - 08522BEC27CF7A0C005FF059 /* Keyboard(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Keyboard(ap).md"; sourceTree = ""; }; - 0858C5C62BEBD230004F4C04 /* ContentUnavailableView(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "ContentUnavailableView(ap).md"; sourceTree = ""; }; - 0858C5C82BECCD17004F4C04 /* SwiftData-资料(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftData-资料(ap).md"; sourceTree = ""; }; - 085BB77327D22FCA00E8F69A /* SwiftUI动画(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI动画(ap).md"; sourceTree = ""; }; - 085BB77527D22FE300E8F69A /* SwiftUI Canvas(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI Canvas(ap).md"; sourceTree = ""; }; - 085F06A02BF73AB80090310F /* Sheet(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Sheet(ap).md"; sourceTree = ""; }; - 08659BC62BE8FD84009B7C00 /* SwiftUI数据流(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI数据流(ap).md"; sourceTree = ""; }; - 08659BCC2BE9A40A009B7C00 /* 创建@Model模型(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "创建@Model模型(ap).md"; sourceTree = ""; }; - 08659BCE2BE9A430009B7C00 /* 容器配置modelContainer(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "容器配置modelContainer(ap).md"; sourceTree = ""; }; - 08659BD02BE9A448009B7C00 /* 增删modelContext(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "增删modelContext(ap).md"; sourceTree = ""; }; - 08659BD22BE9A478009B7C00 /* SwiftData-检索(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftData-检索(ap).md"; sourceTree = ""; }; - 08659BD62BE9A7F8009B7C00 /* SwiftData-处理大量数据(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftData-处理大量数据(ap).md"; sourceTree = ""; }; - 08659BD82BE9A80E009B7C00 /* SwiftData多线程(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftData多线程(ap).md"; sourceTree = ""; }; - 08659BDA2BE9A834009B7C00 /* SwiftData-版本迁移(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftData-版本迁移(ap).md"; sourceTree = ""; }; - 08659BDC2BE9E3AA009B7C00 /* SwiftData-调试(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftData-调试(ap).md"; sourceTree = ""; }; - 08659BDE2BEA4D8C009B7C00 /* SwiftData-模型关系(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftData-模型关系(ap).md"; sourceTree = ""; }; - 086923302BF171A6006779A3 /* ForEach(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "ForEach(ap).md"; sourceTree = ""; }; - 086923332BF178D9006779A3 /* 固定到滚动视图的顶部(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "固定到滚动视图的顶部(ap).md"; sourceTree = ""; }; - 086923352BF18918006779A3 /* 滚动到特定的位置(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "滚动到特定的位置(ap).md"; sourceTree = ""; }; - 086923372BF19AB7006779A3 /* scrollTargetBehavior分页滚动(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "scrollTargetBehavior分页滚动(ap).md"; sourceTree = ""; }; - 086923392BF1A490006779A3 /* scrollTransition视觉效果(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "scrollTransition视觉效果(ap).md"; sourceTree = ""; }; - 0869233B2BF1BF35006779A3 /* ScrollView-参考资料(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "ScrollView-参考资料(ap).md"; sourceTree = ""; }; 0869233F2BF2BF81006779A3 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; 086A5F032744E88E00FECE02 /* 戴铭的开发小册子.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "戴铭的开发小册子.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 086A5F062744E88E00FECE02 /* SwiftPamphletAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftPamphletAppApp.swift; sourceTree = ""; }; @@ -529,126 +190,23 @@ 086A5F432744EE2800FECE02 /* SwiftPamphletAppConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftPamphletAppConfig.swift; sourceTree = ""; }; 086A5F452744EEB900FECE02 /* FundationFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundationFunction.swift; sourceTree = ""; }; 086A5F512744EF4C00FECE02 /* ViewComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewComponent.swift; sourceTree = ""; }; - 086BEEF72BF629DB00025307 /* AnyLayout(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "AnyLayout(ap).md"; sourceTree = ""; }; - 086BEEF92BF6300400025307 /* ViewThatFits(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "ViewThatFits(ap).md"; sourceTree = ""; }; - 086BEEFB2BF63A0000025307 /* Layout协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Layout协议(ap).md"; sourceTree = ""; }; - 086BEEFD2BF644D400025307 /* GeometryReader(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "GeometryReader(ap).md"; sourceTree = ""; }; - 086BEEFF2BF659E500025307 /* alignmentGuide(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "alignmentGuide(ap).md"; sourceTree = ""; }; - 086BEF012BF65DCD00025307 /* 布局进阶-参考资料(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局进阶-参考资料(ap).md"; sourceTree = ""; }; - 086BEF042BF6C38300025307 /* 文字Picker(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "文字Picker(ap).md"; sourceTree = ""; }; - 086BEF062BF6C39B00025307 /* ColorPicker(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "ColorPicker(ap).md"; sourceTree = ""; }; - 086BEF082BF6C3B100025307 /* DatePicker(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "DatePicker(ap).md"; sourceTree = ""; }; - 086BEF0A2BF6C40300025307 /* PhotoPicker(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "PhotoPicker(ap).md"; sourceTree = ""; }; - 086BEF0C2BF6C42500025307 /* 字体Picker(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "字体Picker(ap).md"; sourceTree = ""; }; - 086BEF0E2BF6C43800025307 /* WheelPicker(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "WheelPicker(ap).md"; sourceTree = ""; }; 086D48A02BBB820100835544 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 0871C6182BA040E5000B620D /* InfoRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoRowView.swift; sourceTree = ""; }; 0871C61A2BA04D23000B620D /* CategoryRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryRowView.swift; sourceTree = ""; }; 0871C61C2BA05F44000B620D /* InfosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfosView.swift; sourceTree = ""; }; - 087ECDFD2BFCC3560011F679 /* Full Screen Modal View(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Full Screen Modal View(ap).md"; sourceTree = ""; }; - 087ECDFF2BFCC36F0011F679 /* confirmationDialog()(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "confirmationDialog()(ap).md"; sourceTree = ""; }; - 087ECE012BFCC3980011F679 /* Alert(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Alert(ap).md"; sourceTree = ""; }; - 087ECE032BFCC3AA0011F679 /* Popover(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Popover(ap).md"; sourceTree = ""; }; - 087ECE052BFCC3BD0011F679 /* Menu和ContextMenu(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Menu和ContextMenu(ap).md"; sourceTree = ""; }; - 087ECE072BFCC3D90011F679 /* HUD(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "HUD(ap).md"; sourceTree = ""; }; - 087ECE0D2BFDE0630011F679 /* 自定义修饰符(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "自定义修饰符(ap).md"; sourceTree = ""; }; - 087ECE0F2BFDEA230011F679 /* 背景修饰符(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "背景修饰符(ap).md"; sourceTree = ""; }; - 087ECE112BFDF07A0011F679 /* 修饰符-visualEffect(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "修饰符-visualEffect(ap).md"; sourceTree = ""; }; - 087ECE132BFDF7530011F679 /* 修饰符-圆角(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "修饰符-圆角(ap).md"; sourceTree = ""; }; - 087ECE152BFE01F80011F679 /* ContainerRelativeShape(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "ContainerRelativeShape(ap).md"; sourceTree = ""; }; - 087ECE172BFE0E000011F679 /* 修饰符-fixedSize(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "修饰符-fixedSize(ap).md"; sourceTree = ""; }; - 087ECE192BFEA8210011F679 /* 修饰符-蒙版(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "修饰符-蒙版(ap).md"; sourceTree = ""; }; - 087ECE1B2BFEBF390011F679 /* redacted隐私展示(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "redacted隐私展示(ap).md"; sourceTree = ""; }; - 087ECE1D2BFEC2090011F679 /* SwiftUI-自定义样式(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI-自定义样式(ap).md"; sourceTree = ""; }; - 087ECE1F2BFEC21B0011F679 /* ViewBuilder(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "ViewBuilder(ap).md"; sourceTree = ""; }; - 087ECE212BFED0390011F679 /* 视图协议-简介(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "视图协议-简介(ap).md"; sourceTree = ""; }; - 087ECE242BFF25940011F679 /* 视图协议-核心协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "视图协议-核心协议(ap).md"; sourceTree = ""; }; - 087ECE262BFF25AC0011F679 /* Style协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Style协议(ap).md"; sourceTree = ""; }; - 087ECE282BFF25C00011F679 /* 小组件协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件协议(ap).md"; sourceTree = ""; }; - 087ECE2A2BFF25D30011F679 /* Shapes协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Shapes协议(ap).md"; sourceTree = ""; }; - 087ECE2C2BFF25E70011F679 /* Animations协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Animations协议(ap).md"; sourceTree = ""; }; - 087ECE2E2BFF26000011F679 /* 视图协议-Environment(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "视图协议-Environment(ap).md"; sourceTree = ""; }; - 087ECE302BFF261C0011F679 /* Previews协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Previews协议(ap).md"; sourceTree = ""; }; - 087ECE322BFF264D0011F679 /* Legacy bridges协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Legacy bridges协议(ap).md"; sourceTree = ""; }; - 087ECE342BFF26620011F679 /* Responder chain协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Responder chain协议(ap).md"; sourceTree = ""; }; - 087ECE362BFF26790011F679 /* Toolbar协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Toolbar协议(ap).md"; sourceTree = ""; }; - 087ECE382BFF26910011F679 /* Documents协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Documents协议(ap).md"; sourceTree = ""; }; - 087ECE3A2BFF26B00011F679 /* 特定情况视图协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "特定情况视图协议(ap).md"; sourceTree = ""; }; - 087ECE3C2BFF491B0011F679 /* SwiftUI-Shadow(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI-Shadow(ap).md"; sourceTree = ""; }; - 087ECE3E2BFF8A690011F679 /* Blend Modes(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Blend Modes(ap).md"; sourceTree = ""; }; - 087ECE402C00532F0011F679 /* SwiftUI-渐变(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI-渐变(ap).md"; sourceTree = ""; }; - 087ECE422C0066740011F679 /* SwiftUI-模糊(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI-模糊(ap).md"; sourceTree = ""; }; - 087ECE442C006A170011F679 /* SwiftUI-背景材质(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "SwiftUI-背景材质(ap).md"; sourceTree = ""; }; - 087ECE462C00756C0011F679 /* AppIcon(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "AppIcon(ap).md"; sourceTree = ""; }; - 087ECE482C00DC1E0011F679 /* contentTransition(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "contentTransition(ap).md"; sourceTree = ""; }; - 087ECE4A2C01768B0011F679 /* animation修饰符(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "animation修饰符(ap).md"; sourceTree = ""; }; - 087ECE4C2C018E7B0011F679 /* Transaction(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Transaction(ap).md"; sourceTree = ""; }; - 087ECE4E2C01B7280011F679 /* Matched Geometry Effect(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Matched Geometry Effect(ap).md"; sourceTree = ""; }; - 087ECE502C01D6E10011F679 /* PhaseAnimator(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "PhaseAnimator(ap).md"; sourceTree = ""; }; - 087ECE522C01E5B80011F679 /* KeyframeAnimator(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "KeyframeAnimator(ap).md"; sourceTree = ""; }; - 087ECE542C01F0A10011F679 /* Shaders Metal(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Shaders Metal(ap).md"; sourceTree = ""; }; - 087ECE562C01F3A20011F679 /* 动画-例子(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "动画-例子(ap).md"; sourceTree = ""; }; 0887A5992BA28F6D00131359 /* CSGuideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSGuideView.swift; sourceTree = ""; }; - 0896FB9127BA486900676B7F /* Button(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Button(ap).md"; sourceTree = ""; }; - 08A4FDC127B25A140068E5BC /* @dynamicMemberLookup动态成员查询(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "@dynamicMemberLookup动态成员查询(ap).md"; sourceTree = ""; }; 08A7FF302BEB02EA00E12E5A /* GithubAccessTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubAccessTokenView.swift; sourceTree = ""; }; 08A9E1A12BC25D0700A73764 /* ViewComponentMarkdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewComponentMarkdown.swift; sourceTree = ""; }; 08AEAEF0277F09D000B969E2 /* IntroView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroView.swift; sourceTree = ""; }; 08AEAEF9277F3C7400B969E2 /* css.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = css.html; sourceTree = ""; }; - 08B6B1002BF4FFE8007B6E2D /* 布局-居中(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局-居中(ap).md"; sourceTree = ""; }; - 08B6B1022BF50154007B6E2D /* 布局-offset偏移(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局-offset偏移(ap).md"; sourceTree = ""; }; - 08B6B1042BF5059D007B6E2D /* 布局原理(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局原理(ap).md"; sourceTree = ""; }; - 08B6B1062BF598E8007B6E2D /* Safe Area(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Safe Area(ap).md"; sourceTree = ""; }; - 08BE632927BE220D002BC6A8 /* 全屏模式(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "全屏模式(ap).md"; sourceTree = ""; }; - 08BE632B27BE3762002BC6A8 /* Link(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Link(ap).md"; sourceTree = ""; }; - 08BE632F27BE6CAA002BC6A8 /* Label(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Label(ap).md"; sourceTree = ""; }; - 08BE634127BFAF76002BC6A8 /* TextEditor(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "TextEditor(ap).md"; sourceTree = ""; }; - 08BE634327C3845E002BC6A8 /* TextField(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "TextField(ap).md"; sourceTree = ""; }; - 08BE634927C4BDDB002BC6A8 /* Stack(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Stack(ap).md"; sourceTree = ""; }; - 08BE635327C63828002BC6A8 /* List(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "List(ap).md"; sourceTree = ""; }; - 08BE635727C63F3A002BC6A8 /* ControlGroup(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "ControlGroup(ap).md"; sourceTree = ""; }; - 08BE635B27C65C7C002BC6A8 /* GroupBox(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "GroupBox(ap).md"; sourceTree = ""; }; - 08BE636327C886D2002BC6A8 /* LazyVStack和LazyHStack(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "LazyVStack和LazyHStack(ap).md"; sourceTree = ""; }; - 08BE636727C8C2A0002BC6A8 /* 进度(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "进度(ap).md"; sourceTree = ""; }; - 08BE636B27C8CFA7002BC6A8 /* Image(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Image(ap).md"; sourceTree = ""; }; - 08BE636F27C8F6A7002BC6A8 /* LazyVGrid和LazyHGrid(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "LazyVGrid和LazyHGrid(ap).md"; sourceTree = ""; }; - 08BE637327CCAB52002BC6A8 /* ScrollView(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "ScrollView(ap).md"; sourceTree = ""; }; - 08BE638E27CE157D002BC6A8 /* 浮层(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "浮层(ap).md"; sourceTree = ""; }; - 08C3BB7F27CE4A8500ACF0FE /* TabView(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "TabView(ap).md"; sourceTree = ""; }; - 08C3BBA127CF1B2B00ACF0FE /* Toggle(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Toggle(ap).md"; sourceTree = ""; }; 08CD61FC27758B8A008C0935 /* Lexer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lexer.swift; sourceTree = ""; }; 08CD61FD27758B8A008C0935 /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; - 08D4EBD22BF437510031EDC5 /* Navigation(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Navigation(ap).md"; sourceTree = ""; }; - 08D4EBD42BF4379E0031EDC5 /* NavigationStack(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "NavigationStack(ap).md"; sourceTree = ""; }; - 08D4EBD62BF43C540031EDC5 /* NavigationPath(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "NavigationPath(ap).md"; sourceTree = ""; }; - 08D4EBD82BF44C170031EDC5 /* NavigationSplitView(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "NavigationSplitView(ap).md"; sourceTree = ""; }; - 08D4EBDA2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Inspectors右侧多出一栏(ap).md"; sourceTree = ""; }; - 08D4EBDC2BF461F60031EDC5 /* 自定义导航栏(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "自定义导航栏(ap).md"; sourceTree = ""; }; - 08D4EBDE2BF497030031EDC5 /* 导航状态保存和还原(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "导航状态保存和还原(ap).md"; sourceTree = ""; }; - 08D4EBE12BF4A76A0031EDC5 /* 布局-基础(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局-基础(ap).md"; sourceTree = ""; }; - 08D4EBE32BF4AB9A0031EDC5 /* 布局-留白(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局-留白(ap).md"; sourceTree = ""; }; - 08D4EBE52BF4B8050031EDC5 /* 布局-对齐(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局-对齐(ap).md"; sourceTree = ""; }; 08D8EFE42BED825E00AA0020 /* BookmarkListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkListView.swift; sourceTree = ""; }; - 08D8EFE82BEEF68800AA0020 /* 小组件-StaticConfiguration(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件-StaticConfiguration(ap).md"; sourceTree = ""; }; - 08D8EFEA2BEF106B00AA0020 /* 小组件-AppIntentConfiguration(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件-AppIntentConfiguration(ap).md"; sourceTree = ""; }; - 08D8EFEC2BEF3F2100AA0020 /* 小组件-配置选项(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件-配置选项(ap).md"; sourceTree = ""; }; - 08D8EFEE2BEF45CD00AA0020 /* AppIntentTimelineProvider(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "AppIntentTimelineProvider(ap).md"; sourceTree = ""; }; - 08D8EFF02BEF4E6900AA0020 /* Widget View(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Widget View(ap).md"; sourceTree = ""; }; - 08D8EFF22BEF78B400AA0020 /* 刷新小组件(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "刷新小组件(ap).md"; sourceTree = ""; }; - 08D8EFF52BEF8CAB00AA0020 /* Text-动态时间(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Text-动态时间(ap).md"; sourceTree = ""; }; - 08D8EFF72BEF912700AA0020 /* 小组件动画(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件动画(ap).md"; sourceTree = ""; }; - 08D8EFF92BEF9C9800AA0020 /* 小组件-远程定时获取数据(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件-远程定时获取数据(ap).md"; sourceTree = ""; }; - 08D8EFFB2BEF9EDE00AA0020 /* 小组件-获取位置权限更新内容(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件-获取位置权限更新内容(ap).md"; sourceTree = ""; }; - 08D8EFFD2BEFA3E300AA0020 /* 支持多个小组件(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "支持多个小组件(ap).md"; sourceTree = ""; }; - 08D8EFFF2BEFA72300AA0020 /* 获取小组件形状(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "获取小组件形状(ap).md"; sourceTree = ""; }; - 08D8F0012BEFA84E00AA0020 /* 小组件-Deep link(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件-Deep link(ap).md"; sourceTree = ""; }; - 08D8F0032BEFA86C00AA0020 /* 小组件-参考资料(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件-参考资料(ap).md"; sourceTree = ""; }; 08D8F0062BEFBAC700AA0020 /* WWDCData.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = WWDCData.json; sourceTree = ""; }; 08D8F0092BEFBB2300AA0020 /* WWDCModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WWDCModel.swift; sourceTree = ""; }; 08D8F00B2BEFCFCF00AA0020 /* WWDCListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WWDCListView.swift; sourceTree = ""; }; 08D8F00D2BF044FB00AA0020 /* WWDCDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WWDCDetailView.swift; sourceTree = ""; }; 08ED801B2B9D1EEC0069B7EC /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; - 08EF35CC2BECF3120098E2D4 /* 小组件访问SwiftData(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件访问SwiftData(ap).md"; sourceTree = ""; }; 08EF35D12BECFDA80098E2D4 /* BookmarkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkModel.swift; sourceTree = ""; }; 08F14B432BBE2865005B46CC /* ViewComponentImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewComponentImage.swift; sourceTree = ""; }; 08F51BC427A374A500693AB6 /* footer_js.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = footer_js.html; sourceTree = ""; }; @@ -688,37 +246,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 08026C312869990100792EF1 /* 系统能力 */ = { - isa = PBXGroup; - children = ( - 08026C322869991200792EF1 /* Swift-DocC(ap).md */, - 087ECE462C00756C0011F679 /* AppIcon(ap).md */, - ); - path = "系统能力"; - sourceTree = ""; - }; - 08026C332869999800792EF1 /* 性能和构建 */ = { - isa = PBXGroup; - children = ( - 08026C34286999BB00792EF1 /* 调试(ap).md */, - 08026C3528699AAA00792EF1 /* 内存管理(ap).md */, - 08026C3628699C8700792EF1 /* 链接器(ap).md */, - 08026C402869B1BF00792EF1 /* 性能技术演进(ap).md */, - ); - path = "性能和构建"; - sourceTree = ""; - }; - 0820367E2C073BF8002FB5E3 /* 格式化 */ = { - isa = PBXGroup; - children = ( - 0820367F2C073C0E002FB5E3 /* 格式化-时间(ap).md */, - 082036812C073C24002FB5E3 /* 格式化-数据(ap).md */, - 082036832C075A01002FB5E3 /* 格式化-度量值(ap).md */, - 082036852C075A18002FB5E3 /* 格式化-生活日常(ap).md */, - ); - path = "格式化"; - sourceTree = ""; - }; 08397E212B9EE83F00DFDD02 /* InfoOrganizer */ = { isa = PBXGroup; children = ( @@ -755,129 +282,6 @@ path = "Swift语法"; sourceTree = ""; }; - 08448F47279E7E3A00B61353 /* SwiftUI */ = { - isa = PBXGroup; - children = ( - 08449027279ECEA200B61353 /* 介绍 */, - 08A7FF2D2BEABDCA00E12E5A /* 图文组件 */, - 08A7FF2E2BEABE0100E12E5A /* 数据集合组件 */, - 08A7FF2F2BEABE3F00E12E5A /* 布局组件 */, - 0844902A279ECED300B61353 /* 表单 */, - 085F069E2BF739DC0090310F /* 浮层组件 */, - 085F069F2BF73A020090310F /* 视图组件 */, - 08522BE727CF625B005FF059 /* 视觉 */, - 087ECE092BFDDAD90011F679 /* 修饰符 */, - 087ECE232BFF25200011F679 /* 视图协议 */, - 087ECE1D2BFEC2090011F679 /* SwiftUI-自定义样式(ap).md */, - 087ECE1F2BFEC21B0011F679 /* ViewBuilder(ap).md */, - 08659BC62BE8FD84009B7C00 /* SwiftUI数据流(ap).md */, - ); - path = SwiftUI; - sourceTree = ""; - }; - 08448F48279E7E4100B61353 /* Combine */ = { - isa = PBXGroup; - children = ( - 08448FED279EC92C00B61353 /* 介绍 */, - 08448FF2279EC98800B61353 /* 使用说明 */, - 0844900D279ECBD900B61353 /* 使用场景 */, - ); - path = Combine; - sourceTree = ""; - }; - 08448F4F279E8E9F00B61353 /* macOS */ = { - isa = PBXGroup; - children = ( - 08BE632927BE220D002BC6A8 /* 全屏模式(ap).md */, - 08448F50279E900500B61353 /* macOS剪贴板(ap).md */, - 08448F56279EA84100B61353 /* macOS范例(ap).md */, - 08448F57279EA84100B61353 /* 三栏结构(ap).md */, - 08448F58279EA84100B61353 /* macOS共享菜单(ap).md */, - 08026C3F2869B00D00792EF1 /* macOS技术演进(ap).md */, - ); - path = macOS; - sourceTree = ""; - }; - 08448F5C279EB19700B61353 /* 三方库使用 */ = { - isa = PBXGroup; - children = ( - 08448F5D279EB23600B61353 /* SQLite.swift的使用(ap).md */, - ); - path = "三方库使用"; - sourceTree = ""; - }; - 08448F67279EB40D00B61353 /* 基础库 */ = { - isa = PBXGroup; - children = ( - 08448F7F279EB73C00B61353 /* 系统及设备 */, - 0850445127B110950096D556 /* 自带属性包装 */, - 0850445227B110A50096D556 /* 自带协议 */, - 08448F69279EB47E00B61353 /* 时间(ap).md */, - 08448F6C279EB51000B61353 /* 格式化(ap).md */, - 0820367E2C073BF8002FB5E3 /* 格式化 */, - 08448F6E279EB56400B61353 /* 度量值(ap).md */, - 08448F70279EB58C00B61353 /* Data(ap).md */, - 08448F72279EB5DF00B61353 /* 文件(ap).md */, - 08448F74279EB62B00B61353 /* Scanner(ap).md */, - 08448F76279EB65800B61353 /* AttributeString(ap).md */, - 08448F78279EB68D00B61353 /* 随机(ap).md */, - 08448F7A279EB6CC00B61353 /* UserDefaults(ap).md */, - ); - path = "基础库"; - sourceTree = ""; - }; - 08448F7C279EB6F700B61353 /* 工程模式 */ = { - isa = PBXGroup; - children = ( - 08448F7D279EB71D00B61353 /* 单例(ap).md */, - 08448F91279EB8CA00B61353 /* 程序入口点(ap).md */, - ); - path = "工程模式"; - sourceTree = ""; - }; - 08448F7F279EB73C00B61353 /* 系统及设备 */ = { - isa = PBXGroup; - children = ( - 08448F80279EB75800B61353 /* 系统判断(ap).md */, - 08448F82279EB78A00B61353 /* 版本兼容(ap).md */, - 0850444B27B0D1F80096D556 /* canImport判断库是否可使用(ap).md */, - 0850444D27B0D4EA0096D556 /* targetEnvironment环境的判断(ap).md */, - ); - path = "系统及设备"; - sourceTree = ""; - }; - 08448F87279EB7EC00B61353 /* 网络 */ = { - isa = PBXGroup; - children = ( - 08448F88279EB7FD00B61353 /* 网络状态检查(ap).md */, - ); - path = "网络"; - sourceTree = ""; - }; - 08448F8A279EB82D00B61353 /* 动画 */ = { - isa = PBXGroup; - children = ( - 085BB77327D22FCA00E8F69A /* SwiftUI动画(ap).md */, - 087ECE482C00DC1E0011F679 /* contentTransition(ap).md */, - 087ECE4A2C01768B0011F679 /* animation修饰符(ap).md */, - 087ECE4C2C018E7B0011F679 /* Transaction(ap).md */, - 087ECE4E2C01B7280011F679 /* Matched Geometry Effect(ap).md */, - 087ECE502C01D6E10011F679 /* PhaseAnimator(ap).md */, - 087ECE522C01E5B80011F679 /* KeyframeAnimator(ap).md */, - 08448F8B279EB84800B61353 /* 布局动画(ap).md */, - 087ECE562C01F3A20011F679 /* 动画-例子(ap).md */, - ); - path = "动画"; - sourceTree = ""; - }; - 08448F8D279EB86500B61353 /* 安全 */ = { - isa = PBXGroup; - children = ( - 08448F8E279EB88500B61353 /* Keychain(ap).md */, - ); - path = "安全"; - sourceTree = ""; - }; 08448F93279EB93800B61353 /* 语法基础 */ = { isa = PBXGroup; children = ( @@ -970,85 +374,6 @@ path = "操作符"; sourceTree = ""; }; - 08448FED279EC92C00B61353 /* 介绍 */ = { - isa = PBXGroup; - children = ( - 08448FEE279EC93D00B61353 /* Combine是什么(ap).md */, - 08448FF0279EC96500B61353 /* Combine的资料(ap).md */, - ); - path = "介绍"; - sourceTree = ""; - }; - 08448FF2279EC98800B61353 /* 使用说明 */ = { - isa = PBXGroup; - children = ( - 08448FF3279EC9C500B61353 /* publisher(ap).md */, - 08448FF5279EC9E800B61353 /* Just(ap).md */, - 08448FF7279ECA2000B61353 /* PassthroughSubject(ap).md */, - 08448FF9279ECA5300B61353 /* Empty(ap).md */, - 08448FFB279ECA7C00B61353 /* CurrentValueSubject(ap).md */, - 08448FFD279ECAA100B61353 /* removeDuplicates(ap).md */, - 08448FFF279ECAE000B61353 /* flatMap(ap).md */, - 08449001279ECB0500B61353 /* append(ap).md */, - 08449003279ECB2900B61353 /* prepend(ap).md */, - 08449005279ECB4900B61353 /* merge(ap).md */, - 08449007279ECB6500B61353 /* zip(ap).md */, - 08449009279ECB8C00B61353 /* combineLatest(ap).md */, - 0844900B279ECBB400B61353 /* Scheduler(ap).md */, - ); - path = "使用说明"; - sourceTree = ""; - }; - 0844900D279ECBD900B61353 /* 使用场景 */ = { - isa = PBXGroup; - children = ( - 0844900E279ECC0C00B61353 /* Combine网络请求(ap).md */, - 08449010279ECC3E00B61353 /* Combine KVO(ap).md */, - 08449012279ECC6300B61353 /* Combine通知(ap).md */, - 08449014279ECC8300B61353 /* Combine Timer(ap).md */, - ); - path = "使用场景"; - sourceTree = ""; - }; - 08449016279ECD1400B61353 /* Swift Concurrency */ = { - isa = PBXGroup; - children = ( - 08449017279ECD2400B61353 /* Swift Concurrency是什么(ap).md */, - 08449019279ECD4E00B61353 /* async await(ap).md */, - 0844901B279ECD6B00B61353 /* Async Sequences(ap).md */, - 0844901D279ECD9D00B61353 /* 结构化并发(ap).md */, - 0844901F279ECDCE00B61353 /* Actors(ap).md */, - 08026C302869948400792EF1 /* Distributed Actors(ap).md */, - 08449021279ECDEF00B61353 /* Swift Concurrency相关提案(ap).md */, - 08449023279ECE2200B61353 /* Swift Concurrency学习路径(ap).md */, - 08449025279ECE5400B61353 /* Swift Concurrency和Combine(ap).md */, - 08026C2F2869945300792EF1 /* Concurrency技术演进(ap).md */, - ); - path = "Swift Concurrency"; - sourceTree = ""; - }; - 08449027279ECEA200B61353 /* 介绍 */ = { - isa = PBXGroup; - children = ( - 08449028279ECEB100B61353 /* SwiftUI是什么(ap).md */, - 08026C3828699EB700792EF1 /* SwiftUI参考资料(ap).md */, - 0844902B279ECEFB00B61353 /* SwiftUI对标的UIKit视图(ap).md */, - ); - path = "介绍"; - sourceTree = ""; - }; - 0844902A279ECED300B61353 /* 表单 */ = { - isa = PBXGroup; - children = ( - 08026C3D2869A51200792EF1 /* Form(ap).md */, - 086BEF032BF6BE4E00025307 /* Picker选择器 */, - 08C3BBA127CF1B2B00ACF0FE /* Toggle(ap).md */, - 08522BD927CF5029005FF059 /* Slider(ap).md */, - 08522BDD27CF5133005FF059 /* Stepper(ap).md */, - ); - path = "表单"; - sourceTree = ""; - }; 0846234D2BAC59AF003373D9 /* App */ = { isa = PBXGroup; children = ( @@ -1060,152 +385,6 @@ path = App; sourceTree = ""; }; - 0850445127B110950096D556 /* 自带属性包装 */ = { - isa = PBXGroup; - children = ( - 0850444F27B0FDBD0096D556 /* @resultBuilder(ap).md */, - 08A4FDC127B25A140068E5BC /* @dynamicMemberLookup动态成员查询(ap).md */, - 084E1A5E27B4F7BB0072BBB6 /* @dynamicCallable动态可调用类型(ap).md */, - ); - path = "自带属性包装"; - sourceTree = ""; - }; - 0850445227B110A50096D556 /* 自带协议 */ = { - isa = PBXGroup; - children = ( - 0850445527B110DA0096D556 /* Hashable(ap).md */, - 08448F85279EB7D200B61353 /* JSON没有id字段(ap).md */, - ); - path = "自带协议"; - sourceTree = ""; - }; - 0850AC172BF3574C009FDBBF /* Lazy容器 */ = { - isa = PBXGroup; - children = ( - 08BE636327C886D2002BC6A8 /* LazyVStack和LazyHStack(ap).md */, - 08BE636F27C8F6A7002BC6A8 /* LazyVGrid和LazyHGrid(ap).md */, - ); - path = "Lazy容器"; - sourceTree = ""; - }; - 0850AC1A2BF3A02D009FDBBF /* Table表格 */ = { - isa = PBXGroup; - children = ( - 08026C502869B41B00792EF1 /* Table(ap).md */, - 0850AC1B2BF3A333009FDBBF /* Table-样式(ap).md */, - 0850AC1D2BF3B340009FDBBF /* Table-行的选择(ap).md */, - 0850AC1F2BF3B572009FDBBF /* Table-多属性排序(ap).md */, - 0850AC212BF41F65009FDBBF /* Table-contextMenu(ap).md */, - ); - path = "Table表格"; - sourceTree = ""; - }; - 0850AC232BF436E8009FDBBF /* Navigation导航 */ = { - isa = PBXGroup; - children = ( - 08D4EBD22BF437510031EDC5 /* Navigation(ap).md */, - 08D4EBD42BF4379E0031EDC5 /* NavigationStack(ap).md */, - 08D4EBD62BF43C540031EDC5 /* NavigationPath(ap).md */, - 08D4EBD82BF44C170031EDC5 /* NavigationSplitView(ap).md */, - 08D4EBDC2BF461F60031EDC5 /* 自定义导航栏(ap).md */, - 08D4EBDA2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md */, - 08D4EBDE2BF497030031EDC5 /* 导航状态保存和还原(ap).md */, - ); - path = "Navigation导航"; - sourceTree = ""; - }; - 08522BE727CF625B005FF059 /* 视觉 */ = { - isa = PBXGroup; - children = ( - 08522BE327CF5C55005FF059 /* SwiftUI颜色(ap).md */, - 087ECE3C2BFF491B0011F679 /* SwiftUI-Shadow(ap).md */, - 087ECE3E2BFF8A690011F679 /* Blend Modes(ap).md */, - 087ECE402C00532F0011F679 /* SwiftUI-渐变(ap).md */, - 087ECE422C0066740011F679 /* SwiftUI-模糊(ap).md */, - 087ECE442C006A170011F679 /* SwiftUI-背景材质(ap).md */, - 08522BE827CF6E3A005FF059 /* SwiftUI Effect(ap).md */, - 085BB77527D22FE300E8F69A /* SwiftUI Canvas(ap).md */, - 08026C3E2869AD7800792EF1 /* SF Symbol(ap).md */, - 08026C392869A17800792EF1 /* SwiftCharts(ap).md */, - 087ECE542C01F0A10011F679 /* Shaders Metal(ap).md */, - ); - path = "视觉"; - sourceTree = ""; - }; - 085F069E2BF739DC0090310F /* 浮层组件 */ = { - isa = PBXGroup; - children = ( - 08BE638E27CE157D002BC6A8 /* 浮层(ap).md */, - 085F06A02BF73AB80090310F /* Sheet(ap).md */, - 087ECDFD2BFCC3560011F679 /* Full Screen Modal View(ap).md */, - 087ECDFF2BFCC36F0011F679 /* confirmationDialog()(ap).md */, - 087ECE012BFCC3980011F679 /* Alert(ap).md */, - 087ECE032BFCC3AA0011F679 /* Popover(ap).md */, - 087ECE052BFCC3BD0011F679 /* Menu和ContextMenu(ap).md */, - 087ECE072BFCC3D90011F679 /* HUD(ap).md */, - ); - path = "浮层组件"; - sourceTree = ""; - }; - 085F069F2BF73A020090310F /* 视图组件 */ = { - isa = PBXGroup; - children = ( - 0896FB9127BA486900676B7F /* Button(ap).md */, - 08BE636727C8C2A0002BC6A8 /* 进度(ap).md */, - 08522BEC27CF7A0C005FF059 /* Keyboard(ap).md */, - 08026C3B2869A2F100792EF1 /* Transferable(ap).md */, - 08026C3C2869A35800792EF1 /* ShareLink(ap).md */, - ); - path = "视图组件"; - sourceTree = ""; - }; - 08659BCB2BE9A3E6009B7C00 /* SwiftData */ = { - isa = PBXGroup; - children = ( - 08659BCC2BE9A40A009B7C00 /* 创建@Model模型(ap).md */, - 08659BDE2BEA4D8C009B7C00 /* SwiftData-模型关系(ap).md */, - 08659BCE2BE9A430009B7C00 /* 容器配置modelContainer(ap).md */, - 08659BD02BE9A448009B7C00 /* 增删modelContext(ap).md */, - 08659BD22BE9A478009B7C00 /* SwiftData-检索(ap).md */, - 08659BD62BE9A7F8009B7C00 /* SwiftData-处理大量数据(ap).md */, - 08659BD82BE9A80E009B7C00 /* SwiftData多线程(ap).md */, - 08659BDA2BE9A834009B7C00 /* SwiftData-版本迁移(ap).md */, - 08659BDC2BE9E3AA009B7C00 /* SwiftData-调试(ap).md */, - 0858C5C82BECCD17004F4C04 /* SwiftData-资料(ap).md */, - ); - path = SwiftData; - sourceTree = ""; - }; - 086923322BF17780006779A3 /* Scroll滚动视图 */ = { - isa = PBXGroup; - children = ( - 08BE637327CCAB52002BC6A8 /* ScrollView(ap).md */, - 086923332BF178D9006779A3 /* 固定到滚动视图的顶部(ap).md */, - 086923352BF18918006779A3 /* 滚动到特定的位置(ap).md */, - 086923372BF19AB7006779A3 /* scrollTargetBehavior分页滚动(ap).md */, - 086923392BF1A490006779A3 /* scrollTransition视觉效果(ap).md */, - 0869233B2BF1BF35006779A3 /* ScrollView-参考资料(ap).md */, - ); - path = "Scroll滚动视图"; - sourceTree = ""; - }; - 0869233D2BF1C2CF006779A3 /* List列表 */ = { - isa = PBXGroup; - children = ( - 08BE635327C63828002BC6A8 /* List(ap).md */, - 0850AC112BF3036D009FDBBF /* List-设置样式(ap).md */, - 0850AC052BF2E5DA009FDBBF /* List-移动元素(ap).md */, - 0850AC072BF2E63C009FDBBF /* List-搜索(ap).md */, - 0850AC092BF2F8AE009FDBBF /* List-下拉刷新(ap).md */, - 0850AC0B2BF2FA2F009FDBBF /* List-轻扫操作(ap).md */, - 0850AC0D2BF2FB62009FDBBF /* List-大纲视图(ap).md */, - 0850AC0F2BF30058009FDBBF /* List-完全可点击的行(ap).md */, - 0850AC132BF34D8B009FDBBF /* List-索引标题(ap).md */, - 0850AC152BF35239009FDBBF /* List-加载更多(ap).md */, - ); - path = "List列表"; - sourceTree = ""; - }; 0869233E2BF2BF81006779A3 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -1298,68 +477,6 @@ path = ViewComponet; sourceTree = ""; }; - 086BEEF62BF629C300025307 /* 布局进阶 */ = { - isa = PBXGroup; - children = ( - 086BEEF72BF629DB00025307 /* AnyLayout(ap).md */, - 086BEEF92BF6300400025307 /* ViewThatFits(ap).md */, - 086BEEFB2BF63A0000025307 /* Layout协议(ap).md */, - 086BEEFD2BF644D400025307 /* GeometryReader(ap).md */, - 086BEEFF2BF659E500025307 /* alignmentGuide(ap).md */, - 086BEF012BF65DCD00025307 /* 布局进阶-参考资料(ap).md */, - ); - path = "布局进阶"; - sourceTree = ""; - }; - 086BEF032BF6BE4E00025307 /* Picker选择器 */ = { - isa = PBXGroup; - children = ( - 08522BD527CF3218005FF059 /* Picker(ap).md */, - 086BEF042BF6C38300025307 /* 文字Picker(ap).md */, - 086BEF062BF6C39B00025307 /* ColorPicker(ap).md */, - 086BEF082BF6C3B100025307 /* DatePicker(ap).md */, - 086BEF0A2BF6C40300025307 /* PhotoPicker(ap).md */, - 086BEF0C2BF6C42500025307 /* 字体Picker(ap).md */, - 086BEF0E2BF6C43800025307 /* WheelPicker(ap).md */, - ); - path = "Picker选择器"; - sourceTree = ""; - }; - 087ECE092BFDDAD90011F679 /* 修饰符 */ = { - isa = PBXGroup; - children = ( - 087ECE0D2BFDE0630011F679 /* 自定义修饰符(ap).md */, - 087ECE0F2BFDEA230011F679 /* 背景修饰符(ap).md */, - 087ECE112BFDF07A0011F679 /* 修饰符-visualEffect(ap).md */, - 087ECE132BFDF7530011F679 /* 修饰符-圆角(ap).md */, - 087ECE152BFE01F80011F679 /* ContainerRelativeShape(ap).md */, - 087ECE172BFE0E000011F679 /* 修饰符-fixedSize(ap).md */, - 087ECE192BFEA8210011F679 /* 修饰符-蒙版(ap).md */, - 087ECE1B2BFEBF390011F679 /* redacted隐私展示(ap).md */, - ); - path = "修饰符"; - sourceTree = ""; - }; - 087ECE232BFF25200011F679 /* 视图协议 */ = { - isa = PBXGroup; - children = ( - 087ECE212BFED0390011F679 /* 视图协议-简介(ap).md */, - 087ECE242BFF25940011F679 /* 视图协议-核心协议(ap).md */, - 087ECE262BFF25AC0011F679 /* Style协议(ap).md */, - 087ECE282BFF25C00011F679 /* 小组件协议(ap).md */, - 087ECE2A2BFF25D30011F679 /* Shapes协议(ap).md */, - 087ECE2C2BFF25E70011F679 /* Animations协议(ap).md */, - 087ECE2E2BFF26000011F679 /* 视图协议-Environment(ap).md */, - 087ECE302BFF261C0011F679 /* Previews协议(ap).md */, - 087ECE322BFF264D0011F679 /* Legacy bridges协议(ap).md */, - 087ECE342BFF26620011F679 /* Responder chain协议(ap).md */, - 087ECE362BFF26790011F679 /* Toolbar协议(ap).md */, - 087ECE382BFF26910011F679 /* Documents协议(ap).md */, - 087ECE3A2BFF26B00011F679 /* 特定情况视图协议(ap).md */, - ); - path = "视图协议"; - sourceTree = ""; - }; 0887A5982BA28F3600131359 /* Guide */ = { isa = PBXGroup; children = ( @@ -1370,52 +487,9 @@ path = Guide; sourceTree = ""; }; - 08A7FF2D2BEABDCA00E12E5A /* 图文组件 */ = { - isa = PBXGroup; - children = ( - 08D8EFF42BEF8C9300AA0020 /* Text */, - 08BE632B27BE3762002BC6A8 /* Link(ap).md */, - 08BE632F27BE6CAA002BC6A8 /* Label(ap).md */, - 08BE634127BFAF76002BC6A8 /* TextEditor(ap).md */, - 08BE634327C3845E002BC6A8 /* TextField(ap).md */, - 08BE636B27C8CFA7002BC6A8 /* Image(ap).md */, - ); - path = "图文组件"; - sourceTree = ""; - }; - 08A7FF2E2BEABE0100E12E5A /* 数据集合组件 */ = { - isa = PBXGroup; - children = ( - 086923302BF171A6006779A3 /* ForEach(ap).md */, - 086923322BF17780006779A3 /* Scroll滚动视图 */, - 0869233D2BF1C2CF006779A3 /* List列表 */, - 0850AC172BF3574C009FDBBF /* Lazy容器 */, - 0850AC182BF35A26009FDBBF /* Grid(ap).md */, - 0850AC1A2BF3A02D009FDBBF /* Table表格 */, - ); - path = "数据集合组件"; - sourceTree = ""; - }; - 08A7FF2F2BEABE3F00E12E5A /* 布局组件 */ = { - isa = PBXGroup; - children = ( - 0850AC232BF436E8009FDBBF /* Navigation导航 */, - 08D4EBE02BF4A7470031EDC5 /* 布局基础 */, - 086BEEF62BF629C300025307 /* 布局进阶 */, - 08BE634927C4BDDB002BC6A8 /* Stack(ap).md */, - 08BE635B27C65C7C002BC6A8 /* GroupBox(ap).md */, - 08C3BB7F27CE4A8500ACF0FE /* TabView(ap).md */, - 08BE635727C63F3A002BC6A8 /* ControlGroup(ap).md */, - 08026C3A2869A21000792EF1 /* Advanced layout control(ap).md */, - 0858C5C62BEBD230004F4C04 /* ContentUnavailableView(ap).md */, - ); - path = "布局组件"; - sourceTree = ""; - }; 08AEAEE7277EDD5D00B969E2 /* Guide */ = { isa = PBXGroup; children = ( - 08FC82342C6AE2890083C91F /* appstore */, 08448F44279E7E0300B61353 /* Swift语法 */, 0844902F279ECF7D00B61353 /* 1.md */, ); @@ -1442,20 +516,6 @@ path = Core; sourceTree = ""; }; - 08D4EBE02BF4A7470031EDC5 /* 布局基础 */ = { - isa = PBXGroup; - children = ( - 08D4EBE12BF4A76A0031EDC5 /* 布局-基础(ap).md */, - 08D4EBE32BF4AB9A0031EDC5 /* 布局-留白(ap).md */, - 08D4EBE52BF4B8050031EDC5 /* 布局-对齐(ap).md */, - 08B6B1002BF4FFE8007B6E2D /* 布局-居中(ap).md */, - 08B6B1022BF50154007B6E2D /* 布局-offset偏移(ap).md */, - 08B6B1062BF598E8007B6E2D /* Safe Area(ap).md */, - 08B6B1042BF5059D007B6E2D /* 布局原理(ap).md */, - ); - path = "布局基础"; - sourceTree = ""; - }; 08D8EFE32BED74D900AA0020 /* View */ = { isa = PBXGroup; children = ( @@ -1464,15 +524,6 @@ path = View; sourceTree = ""; }; - 08D8EFF42BEF8C9300AA0020 /* Text */ = { - isa = PBXGroup; - children = ( - 0844902D279ECF1C00B61353 /* Text(ap).md */, - 08D8EFF52BEF8CAB00AA0020 /* Text-动态时间(ap).md */, - ); - path = Text; - sourceTree = ""; - }; 08D8F0052BEFBAA300AA0020 /* WWDC */ = { isa = PBXGroup; children = ( @@ -1500,36 +551,6 @@ path = Setting; sourceTree = ""; }; - 08EF35CA2BECF0B70098E2D4 /* 多线程 */ = { - isa = PBXGroup; - children = ( - 08449016279ECD1400B61353 /* Swift Concurrency */, - 08448F48279E7E4100B61353 /* Combine */, - ); - path = "多线程"; - sourceTree = ""; - }; - 08EF35CB2BECF1690098E2D4 /* 小组件 */ = { - isa = PBXGroup; - children = ( - 08D8EFE82BEEF68800AA0020 /* 小组件-StaticConfiguration(ap).md */, - 08D8EFEA2BEF106B00AA0020 /* 小组件-AppIntentConfiguration(ap).md */, - 08D8EFEC2BEF3F2100AA0020 /* 小组件-配置选项(ap).md */, - 08D8EFEE2BEF45CD00AA0020 /* AppIntentTimelineProvider(ap).md */, - 08D8EFF02BEF4E6900AA0020 /* Widget View(ap).md */, - 08D8EFF22BEF78B400AA0020 /* 刷新小组件(ap).md */, - 08D8EFF72BEF912700AA0020 /* 小组件动画(ap).md */, - 08D8EFF92BEF9C9800AA0020 /* 小组件-远程定时获取数据(ap).md */, - 08D8EFFB2BEF9EDE00AA0020 /* 小组件-获取位置权限更新内容(ap).md */, - 08D8EFFD2BEFA3E300AA0020 /* 支持多个小组件(ap).md */, - 08D8EFFF2BEFA72300AA0020 /* 获取小组件形状(ap).md */, - 08D8F0012BEFA84E00AA0020 /* 小组件-Deep link(ap).md */, - 08D8F0032BEFA86C00AA0020 /* 小组件-参考资料(ap).md */, - 08EF35CC2BECF3120098E2D4 /* 小组件访问SwiftData(ap).md */, - ); - path = "小组件"; - sourceTree = ""; - }; 08EF35CE2BECFCD40098E2D4 /* View */ = { isa = PBXGroup; children = ( @@ -1582,26 +603,6 @@ path = Category; sourceTree = ""; }; - 08FC82342C6AE2890083C91F /* appstore */ = { - isa = PBXGroup; - children = ( - 08448F67279EB40D00B61353 /* 基础库 */, - 08448F47279E7E3A00B61353 /* SwiftUI */, - 08659BCB2BE9A3E6009B7C00 /* SwiftData */, - 08EF35CB2BECF1690098E2D4 /* 小组件 */, - 08026C312869990100792EF1 /* 系统能力 */, - 08448F7C279EB6F700B61353 /* 工程模式 */, - 08EF35CA2BECF0B70098E2D4 /* 多线程 */, - 08448F8A279EB82D00B61353 /* 动画 */, - 08448F87279EB7EC00B61353 /* 网络 */, - 08026C332869999800792EF1 /* 性能和构建 */, - 08448F8D279EB86500B61353 /* 安全 */, - 08448F4F279E8E9F00B61353 /* macOS */, - 08448F5C279EB19700B61353 /* 三方库使用 */, - ); - path = appstore; - sourceTree = ""; - }; 3AE0D5962BAB09AB00D6D925 /* Developer */ = { isa = PBXGroup; children = ( @@ -1726,280 +727,59 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 08026C512869B43500792EF1 /* Table(ap).md in Resources */, - 08D4EBD52BF4379E0031EDC5 /* NavigationStack(ap).md in Resources */, - 08B6B1012BF4FFE8007B6E2D /* 布局-居中(ap).md in Resources */, - 0850AC142BF34D8B009FDBBF /* List-索引标题(ap).md in Resources */, - 0850445027B0FDBD0096D556 /* @resultBuilder(ap).md in Resources */, - 08449015279ECC8300B61353 /* Combine Timer(ap).md in Resources */, - 08448FFC279ECA7C00B61353 /* CurrentValueSubject(ap).md in Resources */, - 087ECE532C01E5B80011F679 /* KeyframeAnimator(ap).md in Resources */, - 08659BD72BE9A7F8009B7C00 /* SwiftData-处理大量数据(ap).md in Resources */, 08448FE4279EC7ED00B61353 /* 范围(ap).md in Resources */, - 08448F92279EB8CA00B61353 /* 程序入口点(ap).md in Resources */, - 087ECE122BFDF07A0011F679 /* 修饰符-visualEffect(ap).md in Resources */, - 08522BED27CF7A0C005FF059 /* Keyboard(ap).md in Resources */, - 08659BDF2BEA4D8C009B7C00 /* SwiftData-模型关系(ap).md in Resources */, - 08D8EFFC2BEF9EDE00AA0020 /* 小组件-获取位置权限更新内容(ap).md in Resources */, 084E1A6327B517FC0072BBB6 /* Swift各版本演进(ap).md in Resources */, - 086BEEFA2BF6300400025307 /* ViewThatFits(ap).md in Resources */, - 08448F6F279EB56400B61353 /* 度量值(ap).md in Resources */, - 0844900C279ECBB400B61353 /* Scheduler(ap).md in Resources */, 08448FA3279EBB1B00B61353 /* 数字(ap).md in Resources */, 08AEAEFA277F3C7400B969E2 /* css.html in Resources */, - 08026C492869B38300792EF1 /* 链接器(ap).md in Resources */, - 087ECE572C01F3A20011F679 /* 动画-例子(ap).md in Resources */, - 0850444E27B0D4EA0096D556 /* targetEnvironment环境的判断(ap).md in Resources */, - 08B6B1032BF50154007B6E2D /* 布局-offset偏移(ap).md in Resources */, - 08C3BBA227CF1B2B00ACF0FE /* Toggle(ap).md in Resources */, 08448FBA279EC3EA00B61353 /* 方法(ap).md in Resources */, 08448FD0279EC5CC00B61353 /* Switch(ap).md in Resources */, 08F51BC527A374A500693AB6 /* footer_js.html in Resources */, 08448FB4279EC37000B61353 /* 类(ap).md in Resources */, 08448FA7279EBB7E00B61353 /* 元组(ap).md in Resources */, - 0850AC0E2BF2FB62009FDBBF /* List-大纲视图(ap).md in Resources */, 08448FCA279EC56D00B61353 /* Guard(ap).md in Resources */, - 085BB77427D22FCA00E8F69A /* SwiftUI动画(ap).md in Resources */, - 08BE634427C3845E002BC6A8 /* TextField(ap).md in Resources */, - 0844901C279ECD6B00B61353 /* Async Sequences(ap).md in Resources */, - 08BE634227BFAF76002BC6A8 /* TextEditor(ap).md in Resources */, - 08522BDE27CF5133005FF059 /* Stepper(ap).md in Resources */, 08448FB8279EC3C200B61353 /* 属性(ap).md in Resources */, - 087ECE1C2BFEBF390011F679 /* redacted隐私展示(ap).md in Resources */, - 0858C5C92BECCD17004F4C04 /* SwiftData-资料(ap).md in Resources */, - 08D8EFED2BEF3F2100AA0020 /* 小组件-配置选项(ap).md in Resources */, - 0844900F279ECC0C00B61353 /* Combine网络请求(ap).md in Resources */, - 087ECE452C006A170011F679 /* SwiftUI-背景材质(ap).md in Resources */, - 086BEF052BF6C38300025307 /* 文字Picker(ap).md in Resources */, - 0850AC192BF35A26009FDBBF /* Grid(ap).md in Resources */, - 087ECE4F2C01B7280011F679 /* Matched Geometry Effect(ap).md in Resources */, 08448F9A279EBA2900B61353 /* 可选(ap).md in Resources */, - 08026C472869B26900792EF1 /* 调试(ap).md in Resources */, - 082036842C075A01002FB5E3 /* 格式化-度量值(ap).md in Resources */, - 08448F8C279EB84800B61353 /* 布局动画(ap).md in Resources */, 08448FC1279EC4B500B61353 /* filter(ap).md in Resources */, - 086BEEFC2BF63A0000025307 /* Layout协议(ap).md in Resources */, - 087ECE182BFE0E000011F679 /* 修饰符-fixedSize(ap).md in Resources */, 08448F9C279EBA8200B61353 /* 闭包(ap).md in Resources */, - 0869233C2BF1BF35006779A3 /* ScrollView-参考资料(ap).md in Resources */, 08448FAF279EC31200B61353 /* 不透明类型(ap).md in Resources */, - 08449029279ECEB100B61353 /* SwiftUI是什么(ap).md in Resources */, - 087ECE432C0066740011F679 /* SwiftUI-模糊(ap).md in Resources */, - 086BEEFE2BF644D400025307 /* GeometryReader(ap).md in Resources */, - 087ECE0E2BFDE0630011F679 /* 自定义修饰符(ap).md in Resources */, - 08448F79279EB68D00B61353 /* 随机(ap).md in Resources */, - 08BE632A27BE220D002BC6A8 /* 全屏模式(ap).md in Resources */, - 08448F5B279EA84100B61353 /* macOS共享菜单(ap).md in Resources */, - 0844900A279ECB8C00B61353 /* combineLatest(ap).md in Resources */, - 08026C4D2869B3A600792EF1 /* Transferable(ap).md in Resources */, - 0869233A2BF1A490006779A3 /* scrollTransition视觉效果(ap).md in Resources */, - 08659BC72BE8FD84009B7C00 /* SwiftUI数据流(ap).md in Resources */, - 08449011279ECC3E00B61353 /* Combine KVO(ap).md in Resources */, - 08D4EBDF2BF497030031EDC5 /* 导航状态保存和还原(ap).md in Resources */, 08448FE2279EC7CF00B61353 /* Nil-coalescing(ap).md in Resources */, - 08449002279ECB0500B61353 /* append(ap).md in Resources */, - 087ECE332BFF264D0011F679 /* Legacy bridges协议(ap).md in Resources */, - 08D4EBDB2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md in Resources */, - 08449013279ECC6300B61353 /* Combine通知(ap).md in Resources */, - 08B6B1052BF5059D007B6E2D /* 布局原理(ap).md in Resources */, - 0844901E279ECD9D00B61353 /* 结构化并发(ap).md in Resources */, 08448FD3279EC60300B61353 /* 数组(ap).md in Resources */, 08448F64279EB32C00B61353 /* Swift规范(ap).md in Resources */, - 08026C482869B28A00792EF1 /* 内存管理(ap).md in Resources */, - 08D8EFF62BEF8CAB00AA0020 /* Text-动态时间(ap).md in Resources */, 08448FD7279EC64600B61353 /* 字典(ap).md in Resources */, - 08026C412869B1BF00792EF1 /* 性能技术演进(ap).md in Resources */, - 0850AC0A2BF2F8AE009FDBBF /* List-下拉刷新(ap).md in Resources */, - 087ECE4B2C01768B0011F679 /* animation修饰符(ap).md in Resources */, - 08026C4F2869B3BE00792EF1 /* Form(ap).md in Resources */, - 08522BE927CF6E3B005FF059 /* SwiftUI Effect(ap).md in Resources */, - 08449004279ECB2900B61353 /* prepend(ap).md in Resources */, - 08BE634A27C4BDDB002BC6A8 /* Stack(ap).md in Resources */, - 08448F89279EB7FD00B61353 /* 网络状态检查(ap).md in Resources */, - 08449018279ECD2400B61353 /* Swift Concurrency是什么(ap).md in Resources */, 08448FAD279EC2E900B61353 /* 泛型和协议(ap).md in Resources */, - 0850AC162BF35239009FDBBF /* List-加载更多(ap).md in Resources */, - 08026C532869B44400792EF1 /* macOS技术演进(ap).md in Resources */, - 087ECE142BFDF7530011F679 /* 修饰符-圆角(ap).md in Resources */, 08448F66279EB33F00B61353 /* Swift书单(ap).md in Resources */, 08448FDA279EC6F500B61353 /* 赋值(ap).md in Resources */, - 08C3BB8027CE4A8500ACF0FE /* TabView(ap).md in Resources */, - 08BE633027BE6CAA002BC6A8 /* Label(ap).md in Resources */, - 08448FF1279EC96500B61353 /* Combine的资料(ap).md in Resources */, - 0850AC122BF3036D009FDBBF /* List-设置样式(ap).md in Resources */, - 08BE637027C8F6A7002BC6A8 /* LazyVGrid和LazyHGrid(ap).md in Resources */, 08448FB6279EC39800B61353 /* 结构体(ap).md in Resources */, - 08BE638F27CE157D002BC6A8 /* 浮层(ap).md in Resources */, - 087ECE512C01D6E10011F679 /* PhaseAnimator(ap).md in Resources */, - 086BEF0B2BF6C40300025307 /* PhotoPicker(ap).md in Resources */, - 08448F6A279EB47E00B61353 /* 时间(ap).md in Resources */, 08448F97279EB9B000B61353 /* 打印(ap).md in Resources */, 08448FC5279EC4F800B61353 /* sorted(ap).md in Resources */, - 0850445627B110DA0096D556 /* Hashable(ap).md in Resources */, - 08449020279ECDCE00B61353 /* Actors(ap).md in Resources */, - 0896FB9227BA486900676B7F /* Button(ap).md in Resources */, - 08026C442869B24F00792EF1 /* Concurrency技术演进(ap).md in Resources */, - 08A4FDC227B25A140068E5BC /* @dynamicMemberLookup动态成员查询(ap).md in Resources */, - 08659BD12BE9A448009B7C00 /* 增删modelContext(ap).md in Resources */, 08448FC3279EC4D600B61353 /* reduce(ap).md in Resources */, - 08BE635C27C65C7C002BC6A8 /* GroupBox(ap).md in Resources */, - 087ECE492C00DC1E0011F679 /* contentTransition(ap).md in Resources */, - 0844901A279ECD4E00B61353 /* async await(ap).md in Resources */, - 08449024279ECE2200B61353 /* Swift Concurrency学习路径(ap).md in Resources */, - 08D8EFF12BEF4E6900AA0020 /* Widget View(ap).md in Resources */, - 08D8EFEF2BEF45CD00AA0020 /* AppIntentTimelineProvider(ap).md in Resources */, - 08BE636827C8C2A0002BC6A8 /* 进度(ap).md in Resources */, - 085F06A12BF73AB80090310F /* Sheet(ap).md in Resources */, - 08BE636C27C8CFA7002BC6A8 /* Image(ap).md in Resources */, - 08659BDD2BE9E3AA009B7C00 /* SwiftData-调试(ap).md in Resources */, - 08659BDB2BE9A834009B7C00 /* SwiftData-版本迁移(ap).md in Resources */, - 0850AC222BF41F65009FDBBF /* Table-contextMenu(ap).md in Resources */, - 08522BE427CF5C55005FF059 /* SwiftUI颜色(ap).md in Resources */, - 087ECE4D2C018E7B0011F679 /* Transaction(ap).md in Resources */, - 08D4EBD32BF437510031EDC5 /* Navigation(ap).md in Resources */, 08448FDE279EC74200B61353 /* 比较运算符(ap).md in Resources */, - 0850AC062BF2E5DA009FDBBF /* List-移动元素(ap).md in Resources */, - 087ECE022BFCC3980011F679 /* Alert(ap).md in Resources */, - 087ECE2D2BFF25E70011F679 /* Animations协议(ap).md in Resources */, - 087ECE372BFF26790011F679 /* Toolbar协议(ap).md in Resources */, - 087ECE3B2BFF26B00011F679 /* 特定情况视图协议(ap).md in Resources */, - 086BEF092BF6C3B100025307 /* DatePicker(ap).md in Resources */, - 08659BCD2BE9A40A009B7C00 /* 创建@Model模型(ap).md in Resources */, - 08449000279ECAE100B61353 /* flatMap(ap).md in Resources */, - 086BEF002BF659E500025307 /* alignmentGuide(ap).md in Resources */, - 08D8EFFE2BEFA3E300AA0020 /* 支持多个小组件(ap).md in Resources */, - 087ECE042BFCC3AA0011F679 /* Popover(ap).md in Resources */, - 086923382BF19AB7006779A3 /* scrollTargetBehavior分页滚动(ap).md in Resources */, 08448FAB279EC2B400B61353 /* 枚举(ap).md in Resources */, - 082036802C073C0E002FB5E3 /* 格式化-时间(ap).md in Resources */, - 08522BD627CF3218005FF059 /* Picker(ap).md in Resources */, - 08659BD92BE9A80E009B7C00 /* SwiftData多线程(ap).md in Resources */, 08448FCE279EC5AA00B61353 /* While(ap).md in Resources */, 08D8F0072BEFBAC700AA0020 /* WWDCData.json in Resources */, - 0850AC082BF2E63C009FDBBF /* List-搜索(ap).md in Resources */, - 087ECE252BFF25940011F679 /* 视图协议-核心协议(ap).md in Resources */, - 08448F71279EB58C00B61353 /* Data(ap).md in Resources */, - 08448FFE279ECAA100B61353 /* removeDuplicates(ap).md in Resources */, - 087ECE472C00756C0011F679 /* AppIcon(ap).md in Resources */, - 08D4EBE62BF4B8050031EDC5 /* 布局-对齐(ap).md in Resources */, - 08D8EFEB2BEF106B00AA0020 /* 小组件-AppIntentConfiguration(ap).md in Resources */, 0850445827B1228E0096D556 /* Result(ap).md in Resources */, - 08449008279ECB6500B61353 /* zip(ap).md in Resources */, - 08448F83279EB78A00B61353 /* 版本兼容(ap).md in Resources */, - 08659BD32BE9A478009B7C00 /* SwiftData-检索(ap).md in Resources */, - 087ECE062BFCC3BD0011F679 /* Menu和ContextMenu(ap).md in Resources */, - 08448F81279EB75800B61353 /* 系统判断(ap).md in Resources */, - 08448FF6279EC9E800B61353 /* Just(ap).md in Resources */, - 08026C452869B25700792EF1 /* Distributed Actors(ap).md in Resources */, - 0844902C279ECEFB00B61353 /* SwiftUI对标的UIKit视图(ap).md in Resources */, - 085BB77627D22FE300E8F69A /* SwiftUI Canvas(ap).md in Resources */, - 08BE635827C63F3A002BC6A8 /* ControlGroup(ap).md in Resources */, - 08BE637427CCAB52002BC6A8 /* ScrollView(ap).md in Resources */, - 086BEF022BF65DCD00025307 /* 布局进阶-参考资料(ap).md in Resources */, - 086923362BF18918006779A3 /* 滚动到特定的位置(ap).md in Resources */, - 08448F73279EB5DF00B61353 /* 文件(ap).md in Resources */, - 087ECE552C01F0A10011F679 /* Shaders Metal(ap).md in Resources */, 08448FC8279EC54300B61353 /* If(ap).md in Resources */, - 08D4EBE22BF4A76A0031EDC5 /* 布局-基础(ap).md in Resources */, - 08448F7E279EB71D00B61353 /* 单例(ap).md in Resources */, - 0850AC1C2BF3A333009FDBBF /* Table-样式(ap).md in Resources */, - 087ECE102BFDEA230011F679 /* 背景修饰符(ap).md in Resources */, - 08448F8F279EB88500B61353 /* Keychain(ap).md in Resources */, - 087ECE2F2BFF26000011F679 /* 视图协议-Environment(ap).md in Resources */, - 086BEF0D2BF6C42500025307 /* 字体Picker(ap).md in Resources */, - 087ECE272BFF25AC0011F679 /* Style协议(ap).md in Resources */, - 08B6B1072BF598E8007B6E2D /* Safe Area(ap).md in Resources */, 08448FA0279EBAD900B61353 /* 访问控制(ap).md in Resources */, - 0850AC0C2BF2FA2F009FDBBF /* List-轻扫操作(ap).md in Resources */, - 087ECE222BFED0390011F679 /* 视图协议-简介(ap).md in Resources */, - 08448FFA279ECA5300B61353 /* Empty(ap).md in Resources */, - 08026C462869B26000792EF1 /* Swift-DocC(ap).md in Resources */, - 087ECE002BFCC36F0011F679 /* confirmationDialog()(ap).md in Resources */, - 08D4EBE42BF4AB9A0031EDC5 /* 布局-留白(ap).md in Resources */, - 087ECE202BFEC21B0011F679 /* ViewBuilder(ap).md in Resources */, - 087ECDFE2BFCC3560011F679 /* Full Screen Modal View(ap).md in Resources */, - 08448FF8279ECA2000B61353 /* PassthroughSubject(ap).md in Resources */, - 08D8EFF82BEF912700AA0020 /* 小组件动画(ap).md in Resources */, - 08D8F0042BEFA86C00AA0020 /* 小组件-参考资料(ap).md in Resources */, - 08448F86279EB7D200B61353 /* JSON没有id字段(ap).md in Resources */, 08448FA5279EBB4100B61353 /* 布尔数(ap).md in Resources */, - 08026C522869B43D00792EF1 /* SF Symbol(ap).md in Resources */, - 08D8F0022BEFA84E00AA0020 /* 小组件-Deep link(ap).md in Resources */, - 0850AC202BF3B572009FDBBF /* Table-多属性排序(ap).md in Resources */, - 084E1A5F27B4F7BB0072BBB6 /* @dynamicCallable动态可调用类型(ap).md in Resources */, - 087ECE292BFF25C00011F679 /* 小组件协议(ap).md in Resources */, 08448FDC279EC71800B61353 /* 计算符(ap).md in Resources */, - 0844902E279ECF1C00B61353 /* Text(ap).md in Resources */, - 08026C4A2869B38F00792EF1 /* SwiftUI参考资料(ap).md in Resources */, - 08D8EFE92BEEF68800AA0020 /* 小组件-StaticConfiguration(ap).md in Resources */, - 08448F5E279EB23600B61353 /* SQLite.swift的使用(ap).md in Resources */, - 08BE635427C63828002BC6A8 /* List(ap).md in Resources */, 08448FEA279EC86700B61353 /* 运算符(ap).md in Resources */, - 08D4EBD72BF43C540031EDC5 /* NavigationPath(ap).md in Resources */, - 08449006279ECB4900B61353 /* merge(ap).md in Resources */, - 08EF35CD2BECF3120098E2D4 /* 小组件访问SwiftData(ap).md in Resources */, 08448FB1279EC33E00B61353 /* 类型转换(ap).md in Resources */, 08448FE6279EC82500B61353 /* 逻辑(ap).md in Resources */, - 08448F51279E900500B61353 /* macOS剪贴板(ap).md in Resources */, - 08D8EFF32BEF78B400AA0020 /* 刷新小组件(ap).md in Resources */, - 08449026279ECE5400B61353 /* Swift Concurrency和Combine(ap).md in Resources */, - 087ECE312BFF261C0011F679 /* Previews协议(ap).md in Resources */, 08448F95279EB96F00B61353 /* 变量(ap).md in Resources */, - 086BEF072BF6C39B00025307 /* ColorPicker(ap).md in Resources */, - 087ECE2B2BFF25D30011F679 /* Shapes协议(ap).md in Resources */, - 08448F6D279EB51000B61353 /* 格式化(ap).md in Resources */, - 08448FF4279EC9C500B61353 /* publisher(ap).md in Resources */, - 082036862C075A18002FB5E3 /* 格式化-生活日常(ap).md in Resources */, 08448FBC279EC41700B61353 /* 继承(ap).md in Resources */, - 0850444C27B0D1F80096D556 /* canImport判断库是否可使用(ap).md in Resources */, - 08448F59279EA84100B61353 /* macOS范例(ap).md in Resources */, - 08659BCF2BE9A430009B7C00 /* 容器配置modelContainer(ap).md in Resources */, 08448FCC279EC58800B61353 /* 遍历For-in(ap).md in Resources */, - 08448F7B279EB6CC00B61353 /* UserDefaults(ap).md in Resources */, - 08448FEF279EC93D00B61353 /* Combine是什么(ap).md in Resources */, 08448FA9279EBBB900B61353 /* 字符串(ap).md in Resources */, 08448FE8279EC84B00B61353 /* 恒等(ap).md in Resources */, - 08026C4C2869B39F00792EF1 /* Advanced layout control(ap).md in Resources */, - 087ECE1E2BFEC2090011F679 /* SwiftUI-自定义样式(ap).md in Resources */, 086A5F0E2744E89100FECE02 /* Preview Assets.xcassets in Resources */, - 086BEEF82BF629DB00025307 /* AnyLayout(ap).md in Resources */, - 08D4EBD92BF44C170031EDC5 /* NavigationSplitView(ap).md in Resources */, 08448F0F2799328700B61353 /* css_cn.html in Resources */, - 086923312BF171A6006779A3 /* ForEach(ap).md in Resources */, - 08448F5A279EA84100B61353 /* 三栏结构(ap).md in Resources */, 08448F9E279EBAA800B61353 /* 函数(ap).md in Resources */, 08448FEC279EC8BE00B61353 /* 注释(ap).md in Resources */, - 08D8F0002BEFA72300AA0020 /* 获取小组件形状(ap).md in Resources */, - 087ECE3F2BFF8A690011F679 /* Blend Modes(ap).md in Resources */, - 08026C4E2869B3B500792EF1 /* ShareLink(ap).md in Resources */, 08026C432869B22E00792EF1 /* Regex(ap).md in Resources */, - 08D4EBDD2BF461F60031EDC5 /* 自定义导航栏(ap).md in Resources */, - 08BE636427C886D2002BC6A8 /* LazyVStack和LazyHStack(ap).md in Resources */, 08449030279ECF7D00B61353 /* 1.md in Resources */, - 087ECE352BFF26620011F679 /* Responder chain协议(ap).md in Resources */, - 087ECE3D2BFF491B0011F679 /* SwiftUI-Shadow(ap).md in Resources */, - 082036822C073C24002FB5E3 /* 格式化-数据(ap).md in Resources */, - 08522BDA27CF5029005FF059 /* Slider(ap).md in Resources */, - 08D8EFFA2BEF9C9800AA0020 /* 小组件-远程定时获取数据(ap).md in Resources */, - 0850AC102BF30058009FDBBF /* List-完全可点击的行(ap).md in Resources */, - 087ECE1A2BFEA8210011F679 /* 修饰符-蒙版(ap).md in Resources */, - 087ECE412C00532F0011F679 /* SwiftUI-渐变(ap).md in Resources */, - 0858C5C72BEBD230004F4C04 /* ContentUnavailableView(ap).md in Resources */, - 086BEF0F2BF6C43800025307 /* WheelPicker(ap).md in Resources */, - 087ECE082BFCC3D90011F679 /* HUD(ap).md in Resources */, - 087ECE162BFE01F80011F679 /* ContainerRelativeShape(ap).md in Resources */, - 087ECE392BFF26910011F679 /* Documents协议(ap).md in Resources */, 08448FD5279EC62700B61353 /* Sets(ap).md in Resources */, - 086923342BF178D9006779A3 /* 固定到滚动视图的顶部(ap).md in Resources */, - 08448F75279EB62B00B61353 /* Scanner(ap).md in Resources */, - 08BE632C27BE3762002BC6A8 /* Link(ap).md in Resources */, 08448FE0279EC7AF00B61353 /* 三元(ap).md in Resources */, - 08448F77279EB65800B61353 /* AttributeString(ap).md in Resources */, - 08449022279ECDEF00B61353 /* Swift Concurrency相关提案(ap).md in Resources */, - 0850AC1E2BF3B340009FDBBF /* Table-行的选择(ap).md in Resources */, 086A5F0B2744E89100FECE02 /* Assets.xcassets in Resources */, 08448FBF279EC45E00B61353 /* map(ap).md in Resources */, - 08026C4B2869B39700792EF1 /* SwiftCharts(ap).md in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SwiftPamphletApp/Guide/View/GuideListView.swift b/SwiftPamphletApp/Guide/View/GuideListView.swift index 60a9fe1b9..54756461a 100644 --- a/SwiftPamphletApp/Guide/View/GuideListView.swift +++ b/SwiftPamphletApp/Guide/View/GuideListView.swift @@ -164,8 +164,7 @@ final class GuideListModel { L(t: "闭包"), L(t: "函数"), L(t: "访问控制"), - L(t: "Regex"), - L(t: "Regex111") + L(t: "Regex") ]), L(t: "基础类型", sub: [ L(t: "数字"), diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\345\244\204\347\220\206\345\244\247\351\207\217\346\225\260\346\215\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\345\244\204\347\220\206\345\244\247\351\207\217\346\225\260\346\215\256(ap).md" deleted file mode 100644 index 49688637a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\345\244\204\347\220\206\345\244\247\351\207\217\346\225\260\346\215\256(ap).md" +++ /dev/null @@ -1,17 +0,0 @@ - -SwiftData 模型上下文有个方法叫 `enumerate()`,可以高效遍历大量数据。 - -```swift -let descriptor = FetchDescriptor
() -... - -do { - try modelContext.enumerate(descriptor, batchSize: 1000) { article in - ... - } -} catch { - print("Failed.") -} -``` - -其中 batchSize 参数是调整批量处理的数量,也就是一次加载多少对象。因此可以通过这个值来权衡内存和IO数量。这个值默认是 5000。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\346\243\200\347\264\242(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\346\243\200\347\264\242(ap).md" deleted file mode 100644 index 3c931c1bd..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\346\243\200\347\264\242(ap).md" +++ /dev/null @@ -1,97 +0,0 @@ - -## `@Query` -使用 `@Query` 会从数据库中获取数据。 - -```swift -@Query private var articles: [Article] -``` - -`@Query` 还支持 filter、sort、order 和 animation 等参数。 - -```swift -@Query(sort: \Article.title, order: .forward) private var articles: [Article] -``` - -sort 可支持多个 SortDescriptor,SwiftData 会按顺序处理。 - -```swift -@Query(sort: [SortDescriptor(\Article.isArchived, order: .forward),SortDescriptor(\Article.updateDate, order: .reverse)]) var articles: [Article] -``` - -## Predicate - -filter 使用的是 `#Predicate` - -```swift -static var now: Date { Date.now } - -@Query(filter: #Predicate
{ article in - article.releaseDate > now -}) var draftArticles: [Article] -``` - -Predicate 支持的内置方法主要有 `contains`、`allSatisfy`、`flatMap`、`filter`、`subscript`、`starts`、`min`、`max`、`localizedStandardContains`、`localizedCompare`、`caseInsensitiveCompare` 等。 - -```swift -@Query(filter: #Predicate
{ article in - article.title.starts(with: "苹果发布会") -}) var articles: [Article] -``` - -需要注意的是 `.isEmpty` 不能使用 `article.title.isEmpty == false` ,否则会崩溃。 - -## FetchDescriptor - -FetchDescriptor 可以在模型中查找数据,而不必在视图层做。 - -```swift -@Model -final class Article { - var title: String - ... - static var all: FetchDescriptor
{ - FetchDescriptor(sortBy: [SortDescriptor(\Article.updateDate, order: .reverse)]) - } -} - -struct SomeView: View { - @Query(Article.all) private var articles: [Article] - ... -} -``` - -## 获取数量而不加载 - -使用 `fetchCount()` 方法,可完成整个计数,且很快,内存占用少。 - -```swift -let descriptor = FetchDescriptor
(predicate: #Predicate { $0.words > 50 }) -let count = (try? modelContext.fetchCount(descriptor)) ?? 0 -``` - -## fetchLimit 限制获取数量 - -```swift -var descriptor = FetchDescriptor
( - predicate: #Predicate { $0.read }, - sortBy: [SortDescriptor(\Article.updateDate, - order: .reverse)]) -descriptor.fetchLimit = 30 -let articles = try context.fetch(descriptor) - -// 翻页 -let pSize = 30 -let pNumber = 1 -var fetchDescriptor = FetchDescriptor
(sortBy: [SortDescriptor(\Article.updateDate, order: .reverse)]) -fetchDescriptor.fetchOffset = pNumber * pSize -fetchDescriptor.fetchLimit = pSize -``` - -## 限制获取的属性 - -只请求要用的属性 - -```swift -var fetchDescriptor = FetchDescriptor
(sortBy: [SortDescriptor(\.updateDate, order: .reverse)]) -fetchDescriptor.propertiesToFetch = [\.title, \.updateDate] -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\346\250\241\345\236\213\345\205\263\347\263\273(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\346\250\241\345\236\213\345\205\263\347\263\273(ap).md" deleted file mode 100644 index 292798782..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\346\250\241\345\236\213\345\205\263\347\263\273(ap).md" +++ /dev/null @@ -1,31 +0,0 @@ - - -使用 ``@Relationship` 添加关系,但是不加这个宏也可以,SwiftData 会自动添加模型之间的关系。 - -```swift -@Model -final class Author { - var name: String - - @Relationship(deleteRule: .cascade, inverse: \Brew.brewer) - var articles: [Article] = [] -} - -@Model -final class Article { - ... - var author: Author -} -``` - -默认情况 deleteRule 是 `.nullify`,这个删除后只会删除引用关系。`.cascade` 会在删除用户后删除其所有文章。 - -SwiftData 可以添加一对一,一对多,多对多的关系。 - -限制关系表数量 -```swift -@Relationship(maximumModelCount: 5) - var articles: [Article] = [] -``` - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\347\211\210\346\234\254\350\277\201\347\247\273(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\347\211\210\346\234\254\350\277\201\347\247\273(ap).md" deleted file mode 100644 index 8fe969756..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\347\211\210\346\234\254\350\277\201\347\247\273(ap).md" +++ /dev/null @@ -1,64 +0,0 @@ - -以下的小改动 SwiftData 会自动执行轻量迁移: - -- 增加模型 -- 增加有默认值的新属性 -- 重命名属性 -- 删除属性 -- 增加或删除 `.externalStorage` 或 `.allowsCloudEncryption` 属性。 -- 增加所有值都是唯一属性为 `.unique` -- 调整关系的删除规则 - -其他情况需要用到版本迁移,版本迁移步骤如下: - -- 用 VersionedSchema 创建 SwiftData 模型的版本 -- 用 SchemaMigrationPlan 对创建的版本进行排序 -- 为每个迁移定义一个迁移阶段 - -设置版本 - -```swift -enum ArticleV1Schema: VersionedSchema { - static var versionIdentifier: String? = "v1" - static var models: [any PersistentModel.Type] { [Article.self] } - - @Model - final class Article { - ... - } -} -``` - -SchemaMigrationPlan 轻量迁移 - -```swift -enum ArticleMigrationPlan: SchemaMigrationPlan { - static var schemas: [any VersionedSchema.Type] { - [ArticleV1Schema.self, ArticleV2Schema.self] - } - - static var stages: [MigrationStage] { - [migrateV1toV2] - } - - static let migrateV1toV2 = MigrationStage.lightweight( - fromVersion: ArticleV1Schema.self, - toVersion: ArticleV2Schema.self - ) -} -``` - -自定义迁移 - -```swift -static let migrateV1toV2 = MigrationStage.custom( - fromVersion: ArticleV1Schema.self, - toVersion: ArticleV2Schema.self, - willMigrate: { context in - // 合并前的处理 - }, - didMigrate: { context in - // 合并后的处理 - } -) -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\350\260\203\350\257\225(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\350\260\203\350\257\225(ap).md" deleted file mode 100644 index 6b5c1a9b8..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\350\260\203\350\257\225(ap).md" +++ /dev/null @@ -1,6 +0,0 @@ - -CoreData 的调试方式依然适用于 SwiftData。 - -你可以设置启动参数来让 CoreData 打印出执行的 SQL 语句。在你的项目中,选择 "Product" -> "Scheme" -> "Edit Scheme",然后在 "Arguments" 标签下的 "Arguments Passed On Launch" 中添加 -com.apple.CoreData.SQLDebug 1。这样,每当 CoreData 执行 SQL 语句时,都会在控制台中打印出来。 - -使用 `-com.apple.CoreData.SQLDebug 3` 获取后台更多信息。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\350\265\204\346\226\231(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\350\265\204\346\226\231(ap).md" deleted file mode 100644 index ec121af8b..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData-\350\265\204\346\226\231(ap).md" +++ /dev/null @@ -1,9 +0,0 @@ - -## WWDC - -23 -- [Dive deeper into SwiftData - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10196) -- [Migrate to SwiftData - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10189) -- [Meet SwiftData - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10187) -- [Model your schema with SwiftData - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10195) -- [Build an app with SwiftData - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10154) diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData\345\244\232\347\272\277\347\250\213(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData\345\244\232\347\272\277\347\250\213(ap).md" deleted file mode 100644 index 53f4e1452..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/SwiftData\345\244\232\347\272\277\347\250\213(ap).md" +++ /dev/null @@ -1,27 +0,0 @@ - -创建一个 Actor,然后 SwiftData 上下文在其中执行操作。 - -```swift -@ModelActor -actor DataHandler {} - -extension DataHandler { - func addInfo() throws -> IOInfo { - let info = IOInfo() - modelContext.insert(info) - try modelContext.save() - return info - } - ... -} -``` - -使用 - -```swift -Task.detached { - let handler = DataHandler() - let item = try await handler.addInfo() - ... -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\210\233\345\273\272@Model\346\250\241\345\236\213(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\210\233\345\273\272@Model\346\250\241\345\236\213(ap).md" deleted file mode 100644 index 60a8706ed..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\210\233\345\273\272@Model\346\250\241\345\236\213(ap).md" +++ /dev/null @@ -1,121 +0,0 @@ - -## 创建 - -用 `@Model` 宏装饰类 -```swift -@Model -final class Article { - let title: String - let author: String - let content: String - let publishedDate: Date - - init(title: String, author: String, content: String, publishedDate: Date) { - self.title = title - self.author = author - self.content = content - self.publishedDate = publishedDate - } -} -``` - -以下数据类型默认支持: -- 基础类型:Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Double, Bool, String, Date, Data 等 -- 复杂的类型:Array, Dictionary, Set, Optional, Enum, Struct, Codable 等 -- 模型关系:一对一、一对多、多对多 - -默认数据库路径: `Data/Library/Application Support/default.store` - -## `@Attribute` - -一些常用的: - -- spotlight:使其能出现在 Spotlight 搜索结果里 -- unique:值是唯一的 -- externalStorage:值存储为二进制数据 -- transient:值不存储 -- encrypt:加密存储 - -使用方法 -```swift -@Attribute(.externalStorage) var imgData: Data? = nil -``` - -二进制会将其存储为单独的文件,然后在数据库中引用文件名。文件会存到 `Data/Library/Application Support/.default_SUPPORT/_EXTERNAL_DATA` 目录下。 - -## `@Transient` 不存 - -如果有的属性不希望进行存储,可以使用 `@Transient` - -```swift -@Model -final class Article { - let title: String - let author: String - @Transient var content: String - ... -} -``` - -## transformable - -SwiftData 除了能够存储字符串和整数这样基本类型,还可以存储更复杂的自定义类型。要存储自定义类型,可用 transformable。 - -```swift -@Model -final class Article { - let title: String - let author: String - let content: String - let publishedDate: Date - @Attribute(.transformable(by: UIColorValueTransformer.self)) var bgColor: UIColor - ... -} -``` - -UIColorValueTransformer 类的实现 - -```swift -class UIColorValueTransformer: ValueTransformer { - - // return data - override func transformedValue(_ value: Any?) -> Any? { - guard let color = value as? UIColor else { return nil } - do { - let data = try NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: true) - return data - } catch { - return nil - } - } - - // return UIColor - override func reverseTransformedValue(_ value: Any?) -> Any? { - guard let data = value as? Data else { return nil } - - do { - let color = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) - return color - } catch { - return nil - } - } -} -``` - -注册 - -```swift -struct SwiftPamphletAppApp: App { - init() { - ValueTransformer.setValueTransformer(UIColorValueTransformer(), forName: NSValueTransformerName("UIColorValueTransformer")) - } - - var body: some Scene { - WindowGroup { - ContentView() - .modelContainer(for: [Article.self]) - } - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\242\236\345\210\240modelContext(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\242\236\345\210\240modelContext(ap).md" deleted file mode 100644 index b41ec7919..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\242\236\345\210\240modelContext(ap).md" +++ /dev/null @@ -1,61 +0,0 @@ - -## 添加保存数据 - -```swift -struct SomeView: View { - @Environment(\.modelContext) var context - ... - - var body: some View { - ... - Button(action: { - self.add() - }, label: { - Text("添加") - }) - } - - func add() { - ... - context.insert(article) - } -} -``` - -默认不用使用 `context.save()`,SwiftData 会自动进行保存,如果不想自动保存,可以在容器中设置 - -```swift -var body: some Scene { - WindowGroup { - ContentView() - } - .modelContainer(for: Article.self, isAutosaveEnabled: false) -} -``` - -## 编辑和删除数据 - -编辑数据使用 `@Bindable`。 - -```swift -struct SomeView: View { - @Bindable var article: Article - @Environment(\.modelContext) private var modelContext - ... - - var body: some View { - Form { - TextField("文章标题", text: $article.title) - ... - } - .toolbar { - ToolbarItem(placement: .destructiveAction) { - Button("删除") { - modelContext.delete(article) - } - } - } - ... - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\256\271\345\231\250\351\205\215\347\275\256modelContainer(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\256\271\345\231\250\351\205\215\347\275\256modelContainer(ap).md" deleted file mode 100644 index ac82aef7e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftData/\345\256\271\345\231\250\351\205\215\347\275\256modelContainer(ap).md" +++ /dev/null @@ -1,164 +0,0 @@ - -## 多模型 - -配置方法 - -```swift -@main -struct SomeApp: App { - - var body: some Scene { - WindowGroup { - ContentView() - } - .modelContainer(for: [Article.self, Author.self]) - } -} -``` - -有关系的两个模型,只需要加父模型,SwiftData 会推断出子模型。 - -## 数据存内存 - -```swift -let configuration = ModelConfiguration(inMemory: true) -let container = try ModelContainer(for: schema, configurations: [configuration]) -``` - -## 数据只读 - -```swift -let config = ModelConfiguration(allowsSave: false) -``` - -## 自定义存储文件和位置 - -如果要指定数据库存储的位置,可以按下面写法: - -```swift -@main -struct SomeApp: App { - var container: ModelContainer - - init() { - do { - let storeURL = URL.documentsDirectory.appending(path: "database.sqlite") - let config = ModelConfiguration(url: storeURL) - container = try ModelContainer(for: Article.self, configurations: config) - } catch { - fatalError("Failed") - } - } - - var body: some Scene { - WindowGroup { - ContentView() - } - .modelContainer(container) - } -} -``` - -## iCloud 支持 - -如果要添加 iCloud 支持,需要先确定模型满足以下条件: -- 没有唯一约束 -- 关系是可选的 -- 有所值有默认值 - -iCloud 支持操作步骤: -- 进入 Signing & Capabilities 中,在 Capability 里选择 iCloud -- 选中 CloudKit 旁边的框 -- 设置 bundle identifier -- 再按 Capability,选择 Background Modes -- 选择 Remote Notifications - -## 指定部分表同步到 iCloud - -使用多个 ModelConfiguration 对象来配置,这样可以指定哪个配置成同步到 iCloud,哪些不同步。 - -## 添加多个配置 - -```swift -@main -struct SomeApp: App { - var container: ModelContainer - init() { - do { - let c1 = ModelConfiguration(for: Article.self) - let c2 = ModelConfiguration(for: Author.self, isStoredInMemoryOnly: true) - container = try ModelContainer(for: Article.self, Author.self, configurations: c1, c2) - } catch { - fatalError("Failed") - } - } - - var body: some Scene { - WindowGroup { - ContentView() - } - .modelContainer(container) - } -} -``` - -## 撤销和重做 - -创建容器时进行指定 -```swift -.modelContainer(for: Article.self, isUndoEnabled: true) -``` - -这样 modelContext 就可以调用撤销和重做函数。 -```swift -struct SomeView: View { - @Environment(\.modelContext) private var context - var body: some View { - Button(action: { - context.undoManager?.undo() - }, label: { - Text("撤销") - }) - } -} -``` - -## context - -View 之外的地方,可以通过 ModelContainer 的 context 属性来获取 modelContext。 - -```swift -let context = container.mainContext -let context = ModelContext(container) -``` - -## 预先导入数据 - -方法如下: - -```swift -.modelContainer(for: Article.self) { result in - do { - let container = try result.get() - - // 先检查有没数据 - let descriptor = FetchDescriptor
() - let existingArticles = try container.mainContext.fetchCount(descriptor) - guard existingArticles == 0 else { return } - - // 读取 bundle 里的文件 - guard let url = Bundle.main.url(forResource: "articles", withExtension: "json") else { - fatalError("Failed") - } - - let data = try Data(contentsOf: url) - let articles = try JSONDecoder().decode([Article].self, from: data) - - for article in articles { - container.mainContext.insert(article) - } - } catch { - print("Failed") - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/SwiftUI-\350\207\252\345\256\232\344\271\211\346\240\267\345\274\217(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/SwiftUI-\350\207\252\345\256\232\344\271\211\346\240\267\345\274\217(ap).md" deleted file mode 100644 index 79616d5e5..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/SwiftUI-\350\207\252\345\256\232\344\271\211\346\240\267\345\274\217(ap).md" +++ /dev/null @@ -1,38 +0,0 @@ - - -要创建自定义按钮样式,我们需要创建一个新类型,该类型遵循 `ButtonStyle` 协议。 - -该协议都要求实现一个名为 `makeBody(configuration:)` 的方法。传递给此方法的配置参数包含了我们正在为其设置样式的按钮的相关信息。 - -我们可以通过 `configuration.label` 获取表示按钮标签的视图,并将我们的样式应用于该标签。 - -```swift -struct ContentView: View { - @State private var isPrivacyMode = true - var body: some View { - Button(action: { - print("按钮被点击!") - }) { - Text("点击我") - } - .buttonStyle(ComicButtonStyle()) - } -} - -struct ComicButtonStyle: ButtonStyle { - func makeBody(configuration: Configuration) -> some View { - HStack { - Spacer() - configuration.label - Spacer() - } - .padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)) - .font(.title).bold() - .background { - Capsule() - .stroke(configuration.isPressed ? .red : .blue, lineWidth: 2) - } - .opacity(configuration.isPressed ? 0.6 : 1) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/SwiftUI\346\225\260\346\215\256\346\265\201(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/SwiftUI\346\225\260\346\215\256\346\265\201(ap).md" deleted file mode 100644 index 672a776aa..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/SwiftUI\346\225\260\346\215\256\346\265\201(ap).md" +++ /dev/null @@ -1,137 +0,0 @@ - - -## 属性 - -SwifUI 中的属性就是常量,不可变。 - -```swift -struct SomeView: View { - let saySomething = "你好世界" - - var body: some View { - Text(saySomething) - } -} -``` - -## @State - -当标记为 @State 的属性的值是可变的,当它改变时,视图会重新渲染。 - -```swift -struct ContentView: View { - @State private var isShowingAlert = false - - var body: some View { - Button("Show Alert") { - isShowingAlert = true - } - .alert(isPresented: $isShowingAlert) { - Alert(title: Text("Hello, SwiftUI!")) - } - } -} -``` - -## @Binding - -@Binding 是一个对其他视图的 @State 属性的引用。它允许我们在不拥有这个状态的情况下修改它。 - -```swift -struct ToggleSwitch: View { - @Binding var isOn: Bool - - var body: some View { - Toggle(isOn: $isOn) { - Text("Toggle Switch") - } - } -} -``` - -## @Observable - -下面的协议和属性包装在 iOS 17 后会被 `@Observable` 这个宏所替代了: - -- `ObservableObject` -- `@ObservedObject` -- `@EnvironmentObject` -- `@Published` - -如果你的程序是在 iOS 17 前写的,可以按照下面的步骤进行迁移: - -- 第一步:删掉 ObservableObject 协议,添加 `@Observable` -- 第二步:删 Published 属性 -- 第三步:给不需要观察的属性添加 `@ObservationIgnored` -- 第四步:将 `@ObservedObject` 替换成 `@State` -- 第五步:将 `@Binding` 改成 `@Bindable` -- 第六步:将 environmentObject 换成 environment - - -## @Environment - -@Environment 是一个从环境中获取系统设置或状态的属性。 - -```swift -struct ContentView: View { - @Environment(\.layoutDirection) var layoutDirection - - var body: some View { - if layoutDirection == .leftToRight { - Text("Left to Right layout") - } else { - Text("Right to Left layout") - } - } -} -``` - -Observable 用于 Environment 的方式,举个例子。 - -有个 Observable 类。 - -```swift -import Observation - -@Observable class Cat { - var isOrange: Bool = false -} -``` - -使用 environment 修饰符 - -```swift -SomeView() - .environment(Cat()) -``` - -使用键路径声明一个 `Environment` 属性 - -```swift -struct SomeView: View { - @Environment(Cat.self) private var cat - - var body: some View { - @Bindable var catBindable = cat - Toggle(isOn: $catBindable.isOrange, label: { - Text("是不是橘猫") - }) - } -} -``` - - -## @AppStorage - -@AppStorage 是一个用于存储用户设置的简单键值存储。当存储的值改变时,视图会重新渲染。 - -```swift -struct ContentView: View { - @AppStorage("username") var username: String = "Guest" - - var body: some View { - Text("Hello, \(username)!") - } -} -``` - diff --git a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/ViewBuilder(ap).md b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/ViewBuilder(ap).md deleted file mode 100644 index 42827d02e..000000000 --- a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/ViewBuilder(ap).md +++ /dev/null @@ -1,117 +0,0 @@ - - -`@ViewBuilder` 是 SwiftUI 中的一个特性,它允许你在函数或计算属性中构建和返回一个或多个视图。这使得你可以创建复杂的视图层次结构,同时保持代码的可读性和简洁性。 - -```swift -struct ContentView: View { - @State private var isPrivacyMode = true - var body: some View { - MovieView(title: "红高粱", poster: Image("evermore")) - } -} - -struct MovieView: View { - var title: String - var poster: Image - - var body: some View { - VStack { - poster - .resizable() - .scaledToFit() - Text(title) - .font(.title) - .foregroundColor(.yellow) - .padding(.top, 8) - footer() - } - .background(Color.black) - .cornerRadius(10) - .padding() - } - - @ViewBuilder - func footer() -> some View { - HStack { - Text("导演: 张艺谋") - .font(.caption) - .foregroundColor(.gray) - Spacer() - Text("评分: 9.0") - .font(.caption) - .foregroundColor(.gray) - } - .padding(.horizontal) - } -} -``` - -`@ViewBuilder` 的一些主要的功能: - -- 支持 `if let` 和 `if case` -- 支持在 'if' 语句中使用多个布尔条件 -- 支持 `switch` 语句 -- 支持 `if #available` 语句 -- 支持处理 `#warning` 和 `#error` -- 支持 `let`/`var` 声明 - -接下来,我们将使用 `@ViewBuilder` 来创建一个可展开的电影卡片视图。其中会用到上面提到的条件判断的功能。 - -```swift -struct ContentView: View { - @State private var isPrivacyMode = true - var body: some View { - MovieCard(poster: Image("evermore"), title: "红高粱", director: "张艺谋", rating: 9.0) { - Text("红高粱是一部由张艺谋执导的电影,讲述了九十年代的中国农村生活。") - .foregroundColor(.white) - .padding() - } - } -} - -struct MovieCard: View { - let poster: Image - let title: String - let director: String - let rating: Double - @ViewBuilder var content: () -> Content - @State private var isExpanded = false - - var body: some View { - VStack { - HStack { - poster - .resizable() - .scaledToFit() - .frame(width: 100, height: 150) - VStack(alignment: .leading) { - Text(title) - .font(.title2) - .foregroundColor(.yellow) - Text("导演: \(director)") - .font(.subheadline) - .foregroundColor(.gray) - Text("评分: \(rating, specifier: "%.1f")") - .font(.subheadline) - .foregroundColor(.gray) - } - .padding(.leading) - } - if isExpanded { - content() - .transition(.move(edge: .bottom)) - } - } - .padding() - .background(Color.black) - .cornerRadius(10) - .onTapGesture { - withAnimation { - isExpanded.toggle() - } - } - } -} -``` - -在这个例子中,我们创建了一个 `MovieCard` 视图,其中包含了一个海报、标题、导演和评分。我们还添加了一个 `content` 属性,用于展示电影的简介。在 `MovieCard` 的主体中,我们使用了 `@ViewBuilder` 来创建一个可展开的电影卡片视图。当点击卡片时,电影的简介会展开或收起。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" deleted file mode 100644 index 7bc61a157..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" +++ /dev/null @@ -1,17 +0,0 @@ -- [WWDC22 SwiftUI 和 UI 库相关专题](https://developer.apple.com/wwdc22/topics/swiftui-ui-frameworks/) -- 官方教程 [Learnning SwiftUI](https://developer.apple.com/tutorials/swiftui-concepts) -- [SwiftUI 主题](https://developer.apple.com/xcode/swiftui/) -- [SwiftUI Session](https://developer.apple.com/videos/swiftui-ui-frameworks) -- [SwiftUI 文档](https://developer.apple.com/documentation/SwiftUI) -- [Learning SwiftUI](https://developer.apple.com/tutorials/swiftui-concepts) 一年一度官方入门教程 -- [Food Truck: Building a SwiftUI multiplatform app](https://developer.apple.com/documentation/swiftui/food_truck_building_a_swiftui_multiplatform_app) 一套代码适配 Mac、iPad 和 iPhone 的官方示例 -- [Reda Lemeden 整理的 WWDC22 所有 SwiftUI 相关内容](https://redalemeden.com/collections/swiftui-2022/) - -session: -- [What’s new in SwiftUI](https://developer.apple.com/videos/play/wwdc2022-10052) - -社区整理的和 SwiftUI 的 digital lounges 内容: -- [WWDC swiftui-lounge](https://onmyway133.com/posts/wwdc-swiftui-lounge/) -- [WWDC 2022: Lessons from the SwiftUI Digital Lounges](https://swiftui-lab.com/digital-lounges-2022/) javier 整理的,做了详细的分类 -- [#swiftui-lounge #wwdc22](https://midnight-beanie-ccb.notion.site/swiftui-lounge-wwdc22-e20094b91f074398ba395c3fa245e63d) - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\345\257\271\346\240\207\347\232\204UIKit\350\247\206\345\233\276(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\345\257\271\346\240\207\347\232\204UIKit\350\247\206\345\233\276(ap).md" deleted file mode 100644 index 8c75dbaf7..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\345\257\271\346\240\207\347\232\204UIKit\350\247\206\345\233\276(ap).md" +++ /dev/null @@ -1,26 +0,0 @@ -如下: - -| SwiftUI | UIKit | -| ----------- | ----------- | -| Text 和 Label | UILabel | -| TextField | UITextField | -| TextEditor | UITextView | -| Button 和 Link | UIButton | -| Image | UIImageView | -| NavigationView | UINavigationController 和 UISplitViewController | -| ToolbarItem | UINavigationItem | -| ScrollView | UIScrollView | -| List | UITableView | -| LazyVGrid 和 LazyHGrid | UICollectionView | -| HStack 和 LazyHStack | UIStack | -| VStack 和 LazyVStack | UIStack | -| TabView | UITabBarController 和 UIPageViewController | -| Toggle | UISwitch | -| Slider | UISlider | -| Stepper | UIStepper | -| ProgressView | UIProgressView 和 UIActivityIndicatorView | -| Picker | UISegmentedControl | -| DatePicker | UIDatePicker | -| Alert | UIAlertController | -| ActionSheet | UIAlertController | -| Map | MapKit | diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\346\230\257\344\273\200\344\271\210(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\346\230\257\344\273\200\344\271\210(ap).md" deleted file mode 100644 index e94f81acc..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\273\213\347\273\215/SwiftUI\346\230\257\344\273\200\344\271\210(ap).md" +++ /dev/null @@ -1,53 +0,0 @@ -对于一个基于UIKit的项目是没有必要全部用SwiftUI重写的,在UIKit里使用SwiftUI的视图非常容易,UIHostingController是UIViewController的子类,可以直接用在UIKit里,因此直接将SwiftUI视图加到UIHostingController中,就可以在UIKit里使用SwiftUI视图了。 - -SwiftUI的布局核心是 GeometryReader、View Preferences和Anchor Preferences。如下图所示: - -![](https://ming1016.github.io/qdimg/240505/whatisswiftui-ap01.png) - -SwiftUI的数据流更适合Redux结构,如下图所示: - -![](https://ming1016.github.io/qdimg/240505/whatisswiftui-ap02.png) - -如上图,Redux结构是真正的单向单数据源结构,易于分割,能充分利用SwiftUI内置的数据流Property Wrapper。UI组件干净、体量小、可复用并且无业务逻辑,因此开发时可以聚焦于UI代码。业务逻辑放在一起,所有业务逻辑和数据Model都在Reducer里。 [ACHNBrowserUI](https://github.com/Dimillian/ACHNBrowserUI) 和 [MovieSwiftUI](https://github.com/Dimillian/MovieSwiftUI) 开源项目都是使用的Redux架构。最近比较瞩目的TCA(The Composable Architecture)也是类Redux/Elm的架构的框架, [项目地址见](https://github.com/pointfreeco/swift-composable-architecture) 。 - -提到数据流就不得不说下苹果公司新出的Combine,对标的是RxSwift,由于是苹果公司官方的库,所以应该优先选择。不过和SwiftUI一样,这两个新库对APP支持最低的系统版本都要求是iOS13及以上。那么怎么能够提前用上SwiftUI和Combine呢?或者说现在使用什么库可以以相同接口方式暂时替换它们,又能在以后改为SwiftUI和Combine时成本最小化呢? - -对于SwiftUI,AcFun自研了声明式UI Ysera,类似SwiftUI的接口,并且重构了AcFun里收藏模块列表视图和交互逻辑,如下图所示: - -![](https://ming1016.github.io/qdimg/240505/whatisswiftui-ap03.png) - -通过上图可以看到,swift代码量相比较OC减少了65%以上,原先使用Objective-C实现的相同功能代码超过了1000行,而Swift重写只需要350行,对于AcFun的业务研发工程师而言,同样的需求实现代码比之前少了至少30%,面对单周迭代这样的节奏,团队也变得更从容。代码可读性增加了,后期功能迭代和维护更容易了,Swift让AcFun驶入了iOS开发生态的“快车道”。 - -SwiftUI全部都是基于Swift的各大可提高开发效率特性完成的,比如前面提到的,能够访问只给语言特性级别行为的Property Wrapper,通过Property Wrapper包装代码逻辑,来降低代码复杂度,除了SwiftUI和Combine里@开头的Property Wrapper外,Swift还自带类似 [@dynamicMemberLookup](https://github.com/apple/swift-evolution/blob/master/proposals/0195-dynamic-member-lookup.md) 和 [@dynamicCallable](https://github.com/apple/swift-evolution/blob/master/proposals/0216-dynamic-callable.md) 这样重量级的Property Wrapper。还有 [ResultBuilder](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md) 这种能够简化语法的特性,有些如GraphQL、REST和Networking实际使用ResultBuilder的 [范例可以参考](https://github.com/carson-katri/awesome-result-builders) 。这些Swift的特性如果也能得到充分利用,即使不用SwiftUI也能使开发效率得到大幅提升。 - -网飞(Netflix)App已使用SwiftUI重构了登录界面,网飞增长团队移动负责人故胤道长记录了SwiftUI在网飞的落地过程,详细描述了 [SwiftUI的收益](https://mp.weixin.qq.com/s/oRPRCx78owLe3_gROYapCw) 。网飞能够直接使用SwiftUI得益于他们最低支持iOS 13系统。 - -不过如最低支持系统低于iOS 13,还有开源项目 [AltSwiftUI](https://github.com/rakutentech/AltSwiftUI) 也实现了SwiftUI的语法和特性,能够向前兼容到iOS 11。 - -[Kuba Suder](https://twitter.com/kuba_suder) 做了一个 [SwiftUI Index/Changelog](https://mackuba.eu/swiftui/changelog) ,从官方文档中提取版本信息,一目了然 SwiftUI 每个版本 view,modifier 还有属性做了哪些增加和改变。当然也包括这次 SwiftUI 4 的更新。还有份对今年更新整理的 cheat sheet [What’s New In SwiftUI for iOS Cheat Sheet - WWDC22](https://bigmountainstudio.github.io/What-is-new-in-SwiftUI/) 。 - -SwiftUI 4 做了大量细节更新,比如添加了后台任务函数 [backgroundTask(_:action:)](https://developer.apple.com/documentation/swiftui/scene/backgroundtask(_:action:)?changes=latest_minor) 。List 改用 UICollectionView。AnyLayout 让 HStack 和 VStack 之间可以自由切换。`scrollDismissesKeyboard()` modifier 可以让键盘在滚动时自动 dismiss。`scrollIndicators()` modifier 可以隐藏 ScrollView 和 List 等视图的滚动指示。defersSystemGestures() modifier 允许我们的手势优先于系统的内置手势。颜色的 `.gradient ` 可以获得很简单的渐变,`Rectangle().fill(.red.gradient)`,还有 `.shadow` 用来创建投影 `Rectangle().fill(.red.shadow(.drop(color: .black, radius: 10)))`,还有 `.inner` 内阴影。`lineLimit()` modifier 支持范围设置。还有一些 modifier 支持 toggle 参数,比如 `.bold()` 和 `.italic()` 等,这样利于运行时进行调整。 - -嵌入 UIKit -示例如下: -```swift -cell.contentConfiguration = UIHostingConfiguration { - VStack { - Image(systemName: "wand.and.stars") - .font(.title) - Text("Like magic!") - .font(.title2).bold() - } - .foregroundStyle(Color.purple) -} -``` - -锁屏的 Widget 和 WatchOS 一样,可以瞟一眼就获取信息。 - -官方指南 [Creating Lock Screen Widgets and Watch Complications](https://developer.apple.com/documentation/WidgetKit/Creating-lock-screen-widgets-and-watch-complications) - -可以将 SwiftUI 的 View 生成图片。 - -官方参考文档 [ImageRenderer](https://developer.apple.com/documentation/swiftui/imagerenderer) - -session [Efficiency awaits: Background tasks in SwiftUI](https://developer.apple.com/videos/play/wwdc2022-10142) 了解如何使用 SwiftUI 后台任务 API 简洁地处理任务。展示如何使用 Swift Concurrency 来处理网络响应、后台刷新等——同时保持性能和功率。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/ContainerRelativeShape(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/ContainerRelativeShape(ap).md" deleted file mode 100644 index e313f720e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/ContainerRelativeShape(ap).md" +++ /dev/null @@ -1,43 +0,0 @@ - -`ContainerRelativeShape` 是 SwiftUI 中的一个视图,它可以创建一个形状,这个形状会根据其包含的容器的形状进行变化。这意味着,如果你将 `ContainerRelativeShape` 用作视图的背景,那么这个背景的形状会自动适应视图的形状。 - -以下是一个简单的使用示例: - -```swift -Text("Hello, World!") - .padding() - .background(ContainerRelativeShape().fill(Color.blue)) -``` - -在这个示例中,`Text` 视图的背景是一个 `ContainerRelativeShape`,并且这个形状被填充了蓝色。由于 `ContainerRelativeShape` 会自动适应其容器的形状,所以这个背景的形状会自动适应 `Text` 视图的形状。 - -`ContainerRelativeShape` 能适应不同容器的形状。 - -以下代码展示怎么为 Widget 创建一个适应不同硬件容器显示不同形状边框的背景: - -```swift -struct ContentView: View { - var body: some View { - ZStack { - Color(.yellow) - - ContainerRelativeShape() - .inset(by: 10) - .fill(Color.indigo) - Image(systemName: "person.and.background.dotted") - .resizable() - .clipShape(ContainerRelativeShape() - .inset(by: 10)) - Text("播放电影") - .padding() - .background(ContainerRelativeShape().fill(Color.white)) - } - } -} -``` - -在这个示例中,我们使用 `ContainerRelativeShape` 来设置 `Color` 视图的背景,然后使用 `inset(by:)` 方法来设置 `ContainerRelativeShape` 的边距。最后,我们使用 `ContainerRelativeShape` 来设置 `Text` 视图的背景。 - - - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/redacted\351\232\220\347\247\201\345\261\225\347\244\272(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/redacted\351\232\220\347\247\201\345\261\225\347\244\272(ap).md" deleted file mode 100644 index 32e86c2f7..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/redacted\351\232\220\347\247\201\345\261\225\347\244\272(ap).md" +++ /dev/null @@ -1,38 +0,0 @@ - -## `.privacySensitive` - -使用 .privacySensitive 修饰符,可以在锁屏时隐藏隐私信息,例如电话号码、电子邮件地址等。这样可以保护用户的隐私信息,避免在锁屏时泄露用户的隐私信息。 - -## `.redacted()` - -`.redacted()` 是一个修饰符,用于将视图的内容替换为占位符。这在加载数据时显示占位符,或者在隐私敏感的情况下隐藏数据特别有用。 - -假设我们有一个显示电影海报的视图,当海报图片正在加载时,我们可以使用 `.redacted()` 修饰符来显示占位符: - -```swift -struct MoviePosterView: View { - @State private var isLoading = true - var body: some View { - Image("movie_poster") - .resizable() - .scaledToFit() - .redacted(reason: isLoading ? .placeholder : []) - } -} -``` - -在这个例子中,当 `isLoading` 为 `true` 时,图片视图的内容将被替换为占位符。当海报图片加载完成,你可以将 `isLoading` 设置为 `false`,这将移除占位符并显示实际的图片。 - -你也可以使用 `.redacted()` 修饰符来隐藏隐私敏感的数据。例如,你可能有一个显示电影演员的视图,但在某些情况下,你希望隐藏这个信息: - -```swift -struct MovieActorView: View { - @State private var isPrivacyMode = true - var body: some View { - Text("Movie Actor") - .redacted(reason: isPrivacyMode ? .placeholder : []) - } -} -``` - -在这个例子中,当 `isPrivacyMode` 为 `true` 时,文本视图的内容将被替换为占位符,从而隐藏电影演员的名字。当隐私模式关闭,你可以将 `isPrivacyMode` 设置为 `false`,这将移除占位符并显示电影演员的名字。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-fixedSize(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-fixedSize(ap).md" deleted file mode 100644 index c5e5f7282..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-fixedSize(ap).md" +++ /dev/null @@ -1,65 +0,0 @@ - -在 SwiftUI 中,`.fixedSize()` 修饰符的作用是使视图保持其理想的大小,而不是根据其父视图的大小和布局进行调整。这对于文本视图特别有用,因为默认情况下,文本视图会尽可能地扩展以填充其父视图的空间,这可能会导致文本被切断。使用 `.fixedSize()` 修饰符可以防止这种情况发生。 - -例如,以下代码创建了一个文本视图,该视图的宽度被限制为 100 点,但由于使用了 `.fixedSize()` 修饰符,所以文本视图会保持其理想的大小,而不会被切断: - -```swift -Text("这是一段很长的文本,如果没有使用 .fixedSize() 修饰符,那么这段文本可能会被切断。") - .frame(width: 100) - .fixedSize() -``` - -在这个例子中,如果没有 `.fixedSize()` 修饰符,那么文本视图的宽度会被限制为 100 点,可能会导致文本被切断。但是由于使用了 `.fixedSize()` 修饰符,所以文本视图会保持其理想的大小,即使这意味着它的宽度会超过 100 点。 - -以下是一个更复杂的示例,展示了如何创建一个自定义的按钮样式,该样式使用 `.fixedSize()` 修饰符来限制按钮的大小,以便容纳每个按钮的理想大小: - -```swift -struct CustomButtonStyle: ButtonStyle { - var backgroundColor: Color - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .textCase(.none) - .font(.system(size: 20)) - .foregroundColor(.white) - .padding() - .frame(maxWidth: .infinity) - .background(backgroundColor, in: Capsule()) - .scaleEffect(configuration.isPressed ? 0.95 : 1.0) - } -} - -struct ContentView: View { - var body: some View { - VStack { - Button(action: {}) { - Text("播放") - } - .buttonStyle(CustomButtonStyle(backgroundColor: .red)) - - Button(action: {}) { - Text("查看详情") - } - .buttonStyle(CustomButtonStyle(backgroundColor: .green)) - - Button(action: {}) { - Text("添加到收藏") - } - .buttonStyle(CustomButtonStyle(backgroundColor: .blue)) - - Button(action: {}) { - Text("分享给朋友们观看") - } - .buttonStyle(CustomButtonStyle(backgroundColor: .orange)) - - Button(action: {}) { - Text("查看评论") - } - .buttonStyle(CustomButtonStyle(backgroundColor: .purple)) - } - .fixedSize() - } -} -``` - -使用 `.fixedSize()` 会调整 `VStack` 的大小,以适应其子视图的理想大小,宽度会等于子视图中最宽的视图的宽度,以此达到子视图中每个按钮的宽度保持一致的效果。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-visualEffect(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-visualEffect(ap).md" deleted file mode 100644 index f107962e2..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-visualEffect(ap).md" +++ /dev/null @@ -1,47 +0,0 @@ - -`visualEffect` 是一个强大的修饰符,它允许开发者在不改变当前布局的前提下,对视图进行一些视觉效果的修改。这意味着,你可以在不影响视图的位置和大小的情况下,对视图进行一些视觉上的调整。 - -这个修饰符的工作方式是,它会在闭包中提供一个 `GeometryProxy` 对象,你可以使用这个对象来获取视图的一些几何信息,如位置和大小。然后,你可以在闭包中对视图应用一些特定的修饰符。 - -有很多种视图修饰符可以使用,如缩放、偏移、模糊、对比度、饱和度、不透明度、旋转等。这些都是视觉效果,现在都可以在 `visualEffect` 闭包中使用。 - -`visualEffect` 修饰符还支持动画。这意味着,你可以在修改视图的视觉效果的同时,添加一些动画效果,使视图的变化更加平滑和自然。 - -以下是一个示例: - -```swift -struct ContentView: View { - @State private var isPlaying = false - - var body: some View { - VStack { - Button("播放/暂停") { - isPlaying.toggle() - } - .padding() - .background(Color.indigo) - .foregroundColor(.white) - .cornerRadius(10) - - Text(isPlaying ? "正在播放电影" : "电影已暂停") - .font(.title) - .foregroundColor(isPlaying ? .indigo : .gray) - .visualEffect { initial, geometry in - initial.scaleEffect( - CGSize( - width: isPlaying ? 1.5 : 1, - height: isPlaying ? 1.5 : 1 - ) - ) - } - .animation(.easeInOut, value: isPlaying) - } - .padding() - } -} -``` - -以上代码中,我们创建了一个 `ContentView` 视图,其中包含一个按钮和一个文本。当点击按钮时,`isPlaying` 的值会切换。我们还在文本视图上使用了 `visualEffect` 修饰符,通过 `initial.scaleEffect` 方法来设置文本的缩放效果。我们还使用了 `animation` 修饰符,通过 `.easeInOut` 动画效果来实现缩放动画。 - - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-\345\234\206\350\247\222(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-\345\234\206\350\247\222(ap).md" deleted file mode 100644 index acc1c3115..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-\345\234\206\350\247\222(ap).md" +++ /dev/null @@ -1,63 +0,0 @@ - - -## cornerRadius - -在修饰符中使用 cornerRadius 设置圆角的示例 - -```swift -struct ContentView: View { - var body: some View { - VStack(alignment: .leading, spacing: 20) { - Text("电影名称:星际穿越") - .foregroundStyle(.white) - .padding(10) - .background { - RoundedRectangle(cornerRadius: 16) - .fill(Color.secondary) - .stroke(Color.yellow, lineWidth: 3) - } - - Text("导演:克里斯托弗·诺兰") - } - .padding(20) - .background(Color.indigo) - .clipShape(RoundedRectangle(cornerRadius: 16)) - } -} -``` - -以上代码中,我们创建了一个 `ContentView` 视图,其中包含了一个 `VStack`,其中包含了两个 `Text`。我们使用了 `foregroundStyle` 修饰符来设置第一个 `Text` 的前景色为白色,然后使用 `padding` 修饰符来设置内边距。接着,我们使用了 `background` 修饰符来设置第一个 `Text` 的背景颜色,然后使用 `RoundedRectangle` 来设置圆角和边框。最后,我们使用了 `clipShape` 修饰符来设置整个 `VStack` 的圆角。 - -## UnevenRoundedRectangle - -iOS 17 开始,SwiftUI 引入 UnevenRoundedRectangle 视图,UnevenRoundedRectangle 可以为每个角指定不同的半径值,让开发者可以创建高度定制化的形状。 - -```swift -struct ContentView: View { - @State private var animate = true - var body: some View { - Button(action: { - // 在这里添加播放电影的代码 - withAnimation { - animate.toggle() - } - }) { - Text("播放电影") - .font(.title) - } - .tint(.white) - .frame(width: 200, height: 80) - .background { - UnevenRoundedRectangle(cornerRadii: .init( - topLeading: animate ? 30.0 : 60, - bottomLeading: animate ? 20.0 : 10, - bottomTrailing: animate ? 30.0 : 15, - topTrailing: animate ? 20.0 : 40), - style: .continuous) - .foregroundStyle(.red) - } - } -} -``` - -在这个例子中,我们创建了一个 `ContentView` 视图,其中包含了一个按钮,当点击按钮时,按钮的圆角会发生变化。通过使用 `UnevenRoundedRectangle` 视图,我们可以为每个角指定不同的半径值,从而创建高度定制化的形状。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-\350\222\231\347\211\210(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-\350\222\231\347\211\210(ap).md" deleted file mode 100644 index 4929be615..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\344\277\256\351\245\260\347\254\246/\344\277\256\351\245\260\347\254\246-\350\222\231\347\211\210(ap).md" +++ /dev/null @@ -1,89 +0,0 @@ - -SwiftUI 中有一些修饰符可以用来做蒙版,比如 `.clipped()`、`.clipShape()` 和 `.mask()`。 - -## `.clipped()` - -`.clipped()` 修饰符用于将视图的内容裁剪到其布局区域。如果视图的内容超出了其布局区域,那么超出的部分将不会被显示。 - -例如,你可能有一个大的图片视图,但你只想在一个小的矩形区域内显示这个图片。你可以使用 `.clipped()` 修饰符来实现这个效果: - -```swift -struct ContentView: View { - var body: some View { - Image("evermore") - .frame(width: 100, height: 100) - .clipped() - } -} -``` - -在这个例子中,`Image` 视图的内容将被裁剪到一个宽度和高度都为 100 的矩形区域内。如果图片的大小超过了这个区域,那么超出的部分将不会被显示。 - -需要注意的是 `.clipped()` 只是视觉上的裁切,如果要限制可点击区域,需要使用 `.contentShape()` 修饰符。 - -## `.clipShape()` - -`.clipShape()` 修饰符用于将视图裁剪为指定的形状。你可以使用任何 `Shape` 类型的对象来裁剪视图,比如 `Circle`、`Rectangle`、`RoundedRectangle` 等。 - -例如,你可以使用 `.clipShape()` 修饰符将一个 `Image` 视图裁剪为星形: - -```swift -struct SomeShape: Shape { - func path(in rect: CGRect) -> Path { - let center = CGPoint(x: rect.midX, y: rect.midY) - let radius = min(rect.width, rect.height) / 2 - let points = 5 - let adjustment = CGFloat.pi / 2 - var path = Path() - - for i in 0.. some View { - self - .font(.largeTitle) - .fontWeight(.bold) - .foregroundColor(.primary) - .shadow(color: .gray, radius: 2, x: 0, y: 2) - } -} - -struct ContentView: View { - @State private var showHUD = false - - var body: some View { - Text("这是一个标题") - .asStylizedTitle() - Text("内容") - } -} -``` - -在这个例子中,我们创建了一个 `asStylizedTitle` 方法,它会将 `Text` 视图修饰为一个带阴影的、又好看的标题。我们设置了字体大小为大标题(`.largeTitle`),字体权重为粗体(`.bold`),字体颜色为主题颜色(`.primary`),并添加了一个灰色的阴影。 - -这样,我们就可以在 `ContentView` 中使用 `asStylizedTitle` 方法来修饰 `Text` 视图,而不需要在每个 `Text` 视图中都设置这些修饰符。 - - -## 自定义 ViewModifier - -代码: - -```swift -struct StylizedTitle: ViewModifier { - var font: Font - var color: Color - var shadowColor: Color - var shadowRadius: CGFloat - var shadowOffset: CGSize - - func body(content: Content) -> some View { - content - .font(font) - .fontWeight(.bold) - .foregroundColor(color) - .shadow(color: shadowColor, radius: shadowRadius, x: shadowOffset.width, y: shadowOffset.height) - } -} - -extension Text { - func stylizedTitle(font: Font = .largeTitle, color: Color = .primary, shadowColor: Color = .gray, shadowRadius: CGFloat = 2, shadowOffset: CGSize = CGSize(width: 0, height: 2)) -> some View { - self.modifier(StylizedTitle(font: font, color: color, shadowColor: shadowColor, shadowRadius: shadowRadius, shadowOffset: shadowOffset)) - } -} - -struct ContentView: View { - @State private var showHUD = false - - var body: some View { - Text("这是一个标题") - .stylizedTitle(font: .title) - Text("内容") - } -} -``` - -在这个修饰符中,我们设置了字体大小为大标题(`.largeTitle`),字体权重为粗体(`.bold`),字体颜色为主题颜色(`.primary`),并添加了一个灰色的阴影。 - -在这个修饰符中,我们添加了五个参数:`font`(字体大小),`color`(字体颜色),`shadowColor`(阴影颜色),`shadowRadius`(阴影半径)和 `shadowOffset`(阴影偏移)。这些参数允许我们在使用修饰符时自定义字体、颜色和阴影。 - -参数也可以是一个闭包,这样我们就可以在修饰符中使用动态值。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Image(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Image(ap).md" deleted file mode 100644 index 3c747e193..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Image(ap).md" +++ /dev/null @@ -1,72 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/image-ap01.png) - -## Image 使用 - -```swift -struct PlayImageView: View { - var body: some View { - Image("logo") - .resizable() - .frame(width: 100, height: 100) - - Image("logo") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 50, height: 50) - .clipShape(Circle()) - .overlay( - Circle().stroke(.cyan, lineWidth: 4) - ) - .shadow(radius: 10) - - // SF Symbols - Image(systemName: "scissors") - .imageScale(.large) - .foregroundColor(.pink) - .frame(width: 40, height: 40) - - // SF Symbols 多色时使用原色 - Image(systemName: "thermometer.sun.fill") - .renderingMode(.original) - .imageScale(.large) - } -} -``` - -## AsyncImage - -```swift -struct AsyncImageWithPlaceholder: View { - enum Size { - case tinySize, smallSize,normalSize, bigSize - - var v: CGFloat { - switch self { - case .tinySize: - return 20 - case .smallSize: - return 40 - case .normalSize: - return 60 - case .bigSize: - return 100 - } - } - } - var size: Size - var url: String - var body: some View { - AsyncImage(url: URL(string: url), content: { image in - image - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: size.v, height: size.v) - .cornerRadius(5) - }, - placeholder: { - Image(systemName: "person") - .frame(width: size.v, height: size.v) - }) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Label(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Label(ap).md" deleted file mode 100644 index d33a31ec1..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Label(ap).md" +++ /dev/null @@ -1,93 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/label-ap01.png) - -```swift -struct PlayLabelView: View { - var body: some View { - VStack(spacing: 10) { - Label("一个 Label", systemImage: "bolt.circle") - - Label("只显示 icon", systemImage: "heart.fill") - .labelStyle(.iconOnly) - .foregroundColor(.red) - - // 自建 Label - Label { - Text("自建 Label") - .foregroundColor(.orange) - .bold() - .font(.largeTitle) - .shadow(color: .black, radius: 1, x: 0, y: 2) - } icon: { - Image("p3") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 30) - .shadow(color: .black, radius: 1, x: 0, y: 2) - } - - - // 自定义 LabelStyle - Label("有边框的 Label", systemImage: "b.square.fill") - .labelStyle(.border) - - Label("仅标题有边框", systemImage: "text.bubble") - .labelStyle(.borderOnlyTitle) - - // 扩展的 Label - Label("扩展的 Label", originalSystemImage: "cloud.sun.bolt.fill") - - } // end VStack - } // end body -} - -// 对 Label 做扩展 -extension Label where Title == Text, Icon == Image { - init(_ title: LocalizedStringKey, originalSystemImage systemImageString: String) { - self.init { - Text(title) - } icon: { - Image(systemName: systemImageString) - .renderingMode(.original) // 让 SFSymbol 显示本身的颜色 - } - - } -} - -// 添加自定义 LabelStyle,用来加上边框 -struct BorderLabelStyle: LabelStyle { - func makeBody(configuration: Configuration) -> some View { - Label(configuration) - .padding() - .overlay(RoundedRectangle(cornerRadius: 20) - .stroke(.purple, lineWidth: 4)) - .shadow(color: .black, radius: 4, x: 0, y: 5) - .labelStyle(.automatic) // 样式擦除器,防止样式被 .iconOnly、.titleOnly 这样的 LabelStyle 擦除了样式。 - - } -} -extension LabelStyle where Self == BorderLabelStyle { - internal static var border: BorderLabelStyle { - BorderLabelStyle() - } -} - -// 只给标题加边框 -struct BorderOnlyTitleLabelStyle: LabelStyle { - func makeBody(configuration: Configuration) -> some View { - HStack { - configuration.icon - configuration.title - .padding() - .overlay(RoundedRectangle(cornerRadius: 20) - .stroke(.pink, lineWidth: 4)) - .shadow(color: .black, radius: 1, x: 0, y: 1) - .labelStyle(.automatic) - } - } -} -extension LabelStyle where Self == BorderOnlyTitleLabelStyle { - internal static var borderOnlyTitle: BorderOnlyTitleLabelStyle { - BorderOnlyTitleLabelStyle() - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Link(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Link(ap).md" deleted file mode 100644 index 0da74fdcb..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Link(ap).md" +++ /dev/null @@ -1,88 +0,0 @@ -使用方法如下: - -```swift -struct PlayLinkView: View { - @Environment(\.openURL) var openURL - var aStr: AttributedString { - var a = AttributedString("戴铭的博客") - a.link = URL(string: "https://ming1016.github.io/") - return a - } - var body: some View { - VStack { - // 普通 - Link("前往 www.starming.com", destination: URL(string: "http://www.starming.com")!) - .buttonStyle(.borderedProminent) - Link(destination: URL(string: "https://twitter.com/daiming_cn")!) { - Label("My Twitter", systemImage: "message.circle.fill") - } - - // AttributedString 链接 - Text(aStr) - - // markdown 链接 - Text("[Go Ming's GitHub](https://github.com/ming1016)") - - // 控件使用 OpenURL - Link("小册子源码", destination: URL(string: "https://github.com/ming1016/SwiftPamphletApp")!) - .environment(\.openURL, OpenURLAction { url in - return .systemAction - /// return .handled 不会返回系统打开浏览器动作,只会处理 return 前的事件。 - /// .discard 和 .handled 类似。 - /// .systemAction(URL(string: "https://www.anotherurl.com")) 可以返回另外一个 url 来替代指定的url - }) - - // 扩展 View 后更简洁的使用 OpenURL - Link("戴铭的微博", destination: URL(string: "https://weibo.com/allstarming")!) - .goOpenURL { url in - print(url.absoluteString) - return .systemAction - } - - // 根据内容返回不同链接 - Text("戴铭博客有好几个,存在[GitHub Page](github)、[自建服务器](starming)和[知乎](zhihu)上") - .environment(\.openURL, OpenURLAction { url in - switch url.absoluteString { - case "github": - return .systemAction(URL(string: "https://ming1016.github.io/")!) - case "starming": - return .systemAction(URL(string: "http://www.starming.com")!) - case "zhihu": - return .systemAction(URL(string: "https://www.zhihu.com/people/starming/posts")!) - default: - return .handled - } - }) - } // end VStack - .padding() - - } - - // View 支持 openURL 的能力 - func goUrl(_ url: URL, done: @escaping (_ accepted: Bool) -> Void) { - openURL(url, completion: done) - } -} - -// 为 View 扩展一个 OpenURL 方法 -extension View { - func goOpenURL(done: @escaping (URL) -> OpenURLAction.Result) -> some View { - environment(\.openURL, OpenURLAction(handler: done)) - } -} -``` - -View 的 onOpenURL 方法可以处理 Universal Links。 - -```swift -struct V: View { - var body: some View { - VStack { - Text("hi") - } - .onOpenURL { url in - print(url.absoluteString) - } - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Text/Text(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Text/Text(ap).md" deleted file mode 100644 index 60e7347ab..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Text/Text(ap).md" +++ /dev/null @@ -1,388 +0,0 @@ -基本用法 - -![](https://ming1016.github.io/qdimg/Text-ap01.png) - -```swift -// MARK: - Text -struct PlayTextView: View { - let manyString = "这是一段长文。总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么,总得说点什么吧。" - var body: some View { - ScrollView { - Group { - Text("大标题").font(.largeTitle) - Text("说点啥呢?") - .tracking(30) // 字间距 - .kerning(30) // 尾部留白 - Text("划重点") - .underline() - .foregroundColor(.yellow) - .fontWeight(.heavy) - Text("可旋转的文字") - .rotationEffect(.degrees(45)) - .fixedSize() - .frame(width: 20, height: 80) - Text("自定义系统字体大小") - .font(.system(size: 30)) - Text("使用指定的字体") - .font(.custom("Georgia", size: 24)) - } - Group { - Text("有阴影") - .font(.largeTitle) - .foregroundColor(.orange) - .bold() - .italic() - .shadow(color: .black, radius: 1, x: 0, y: 2) - Text("Gradient Background") - .font(.largeTitle) - .padding() - .foregroundColor(.white) - .background(LinearGradient(gradient: Gradient(colors: [.white, .black, .red]), startPoint: .top, endPoint: .bottom)) - .cornerRadius(10) - Text("Gradient Background") - .padding(5) - .foregroundColor(.white) - .background(LinearGradient(gradient: Gradient(colors: [.white, .black, .purple]), startPoint: .leading, endPoint: .trailing)) - .cornerRadius(10) - ZStack { - Text("渐变透明材质风格") - .padding() - .background( - .regularMaterial, - in: RoundedRectangle(cornerRadius: 10, style: .continuous) - ) - .shadow(radius: 10) - .padding() - .font(.largeTitle.weight(.black)) - } - .frame(width: 300, height: 200) - .background( - LinearGradient(colors: [.yellow, .pink], startPoint: .topLeading, endPoint: .bottomTrailing) - ) - Text("Angular Gradient Background") - .padding() - .background(AngularGradient(colors: [.red, .yellow, .green, .blue, .purple, .red], center: .center)) - .cornerRadius(20) - Text("带背景图片的") - .padding() - .font(.largeTitle) - .foregroundColor(.white) - .background { - Rectangle() - .fill(Color(.black)) - .cornerRadius(10) - Image("logo") - .resizable() - .frame(width: 100, height: 100) - } - .frame(width: 200, height: 100) - } - - Group { - // 设置 lineLimit 表示最多支持行数,依据情况依然有会被减少显示行数 - Text(manyString) - .lineLimit(3) // 对行的限制,如果多余设定行数,尾部会显示... - .lineSpacing(10) // 行间距 - .multilineTextAlignment(.leading) // 对齐 - - // 使用 fixedSize 就可以在任何时候完整显示 - Text(manyString) - .fixedSize(horizontal: false, vertical: true) - - } - - // 使用 AttributeString - PTextViewAttribute() - .padding() - - // 使用 Markdown - PTextViewMarkdown() - .padding() - - // 时间 - PTextViewDate() - - // 插值 - PTextViewInterpolation() - } - - } -} -``` - -font 字体设置的样式对应 weight 和 size 可以在官方交互文档中查看 [Typography](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/typography/#dynamic-type-sizes) - -markdown 使用 -```swift -// MARK: - Markdown -struct PTextViewMarkdown: View { - let mdaStr: AttributedString = { - - var mda = AttributedString(localized: "这是一个 **Attribute** ~string~") - - /// 自定义的属性语法是^[string](key:value) - mda = AttributedString(localized: "^[这是](p2:'one')^[一](p3:{k1:1,k2:2})个 **Attribute** ~string~", including: \.newScope) - print(mda) - /// 这是 { - /// NSLanguage = en - /// p2 = one - /// } - /// 一 { - /// NSLanguage = en - /// p3 = P3(k1: 1, k2: 2) - /// } - /// 个 { - /// NSLanguage = en - /// } - /// Attribute { - /// NSLanguage = en - /// NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 2) - /// } - /// { - /// NSLanguage = en - /// } - /// string { - /// NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 32) - /// NSLanguage = en - /// } - - // 从文件中读取 Markdown 内容 - let mdUrl = Bundle.main.url(forResource: "1", withExtension: "md")! - mda = try! AttributedString(contentsOf: mdUrl,options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace), baseURL: nil) // .inlineOnlyPreservingWhitespace 支持 markdown 文件的换行 - - // Markdown 已转换成 AtrributedString 结构。 - for r in mda.runs { - if let ipi = r.inlinePresentationIntent { - switch ipi { - case .lineBreak: - print("paragrahp") - case .code: - print("this is code") - default: - break - } - } - if let pi = r.presentationIntent { - for c in pi.components { - switch c.kind { - case .paragraph: - print("this is paragraph") - case .codeBlock(let lang): - print("this is \(lang ?? "") code") - case .header(let level): - print("this is \(level) level") - default: - break - } - } - } - } - - return mda - }() - var body: some View { - Text(mdaStr) - } -} -``` - -AttributedString 的使用 -```swift -// MARK: - AttributedString -struct PTextViewAttribute: View { - let aStr: AttributedString = { - var a1 = AttributedString("这是一个 ") - var c1 = AttributeContainer() - c1.font = .footnote - c1.foregroundColor = .secondary - a1.setAttributes(c1) - - var a2 = AttributedString("Attribute ") - var c2 = AttributeContainer() - c2.font = .title - a2.setAttributes(c2) - - var a3 = AttributedString("String ") - var c3 = AttributeContainer() - c3.baselineOffset = 10 - c3.appKit.foregroundColor = .yellow // 仅在 macOS 里显示的颜色 - c3.swiftUI.foregroundColor = .secondary - c3.font = .footnote - a3.setAttributes(c3) - // a3 使用自定义属性 - a3.p1 = "This is a custom property." - - // formatter 的支持 - var a4 = Date.now.formatted(.dateTime - .hour() - .minute() - .weekday() - .attributed - ) - - let c4AMPM = AttributeContainer().dateField(.amPM) - let c4AMPMColor = AttributeContainer().foregroundColor(.green) - - a4.replaceAttributes(c4AMPM, with: c4AMPMColor) - let c4Week = AttributeContainer().dateField(.weekday) - let c4WeekColor = AttributeContainer().foregroundColor(.purple) - a4.replaceAttributes(c4Week, with: c4WeekColor) - - a1.append(a2) - a1.append(a3) - a1.append(a4) - - - - // Runs 视图 - for r in a1.runs { - print(r) - } - /// 这是一个 { - /// SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7ff91d4a5e90).FontBox) - /// SwiftUI.ForegroundColor = secondary - /// } - /// Attribute { - /// SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7ff91d4a5e90).FontBox) - /// } - /// String { - /// SwiftUI.ForegroundColor = secondary - /// SwiftUI.BaselineOffset = 10.0 - /// NSColor = sRGB IEC61966-2.1 colorspace 1 1 0 1 - /// SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7ff91d4a5e90).FontBox) - /// p1 = This is a custom property. - /// } - /// Tue { - /// SwiftUI.ForegroundColor = purple - /// } - /// { - /// } - /// 5 { - /// Foundation.DateFormatField = hour - /// } - /// : { - /// } - /// 16 { - /// Foundation.DateFormatField = minute - /// } - /// { - /// } - /// PM { - /// SwiftUI.ForegroundColor = green - /// } - - return a1 - }() - var body: some View { - Text(aStr) - } -} - -// MARK: - 自定 AttributedString 属性 -struct PAKP1: AttributedStringKey { - typealias Value = String - static var name: String = "p1" - - -} -struct PAKP2: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { - public enum P2: String, Codable { - case one, two, three - } - - static var name: String = "p2" - typealias Value = P2 -} -struct PAKP3: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { - public struct P3: Codable, Hashable { - let k1: Int - let k2: Int - } - typealias Value = P3 - static var name: String = "p3" -} -extension AttributeScopes { - public struct NewScope: AttributeScope { - let p1: PAKP1 - let p2: PAKP2 - let p3: PAKP3 - } - var newScope: NewScope.Type { - NewScope.self - } -} - -extension AttributeDynamicLookup{ - subscript(dynamicMember keyPath:KeyPath) -> T where T:AttributedStringKey { - self[T.self] - } -} -``` - - -时间的显示 - -```swift -// MARK: - 时间 -struct PDateTextView: View { - let date: Date = Date() - let df: DateFormatter = { - let df = DateFormatter() - df.dateStyle = .long - df.timeStyle = .short - return df - }() - var dv: String { - return df.string(from: date) - } - var body: some View { - HStack { - Text(dv) - } - .environment(\.locale, Locale(identifier: "zh_cn")) - } -} -``` - -插值使用 - -```swift -// MARK: - 插值 -struct PTextViewInterpolation: View { - let nf: NumberFormatter = { - let f = NumberFormatter() - f.numberStyle = .currencyPlural - return f - }() - var body: some View { - VStack { - Text("图文 \(Image(systemName: "sun.min"))") - Text("💰 \(999 as NSNumber, formatter: nf)") - .environment(\.locale, Locale(identifier: "zh_cn")) - Text("数组: \(["one", "two"])") - Text("红字:\(red: "变红了"),带图标的字:\(sun: "天晴")") - } - } -} - -// 扩展 LocalizedStringKey.StringInterpolation 自定义插值 -extension LocalizedStringKey.StringInterpolation { - // 特定类型处理 - mutating func appendInterpolation(_ value: [String]) { - for s in value { - appendLiteral(s + "") - appendInterpolation(Text(s + " ").bold().foregroundColor(.secondary)) - } - } - - // 实现不同情况处理,可以简化设置修改器设置 - mutating func appendInterpolation(red value: LocalizedStringKey) { - appendInterpolation(Text(value).bold().foregroundColor(.red)) - } - mutating func appendInterpolation(sun value: String) { - appendInterpolation(Image(systemName: "sun.max.fill")) - appendLiteral(value) - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Text/Text-\345\212\250\346\200\201\346\227\266\351\227\264(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Text/Text-\345\212\250\346\200\201\346\227\266\351\227\264(ap).md" deleted file mode 100644 index 15a11b0e8..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/Text/Text-\345\212\250\346\200\201\346\227\266\351\227\264(ap).md" +++ /dev/null @@ -1,43 +0,0 @@ - -逻辑更新时,不会触发 `body` 的重绘,只对使用了 Text Date 插值的 `Text`。  - -倒计时 - -```swift -Text(Date().addingTimeInterval(60), style: .offset) -``` - -指定多长时间 - -```swift -// 多久后 -let aimDate = Calendar.current.date(byAdding: DateComponents(minute: 10), to: Date())! - -``` - -指定一个具体时间 - -```swift -// 具体时间 -let aimDate = Calendar.current(DateComponents(year: 2024, month: 6, day: 11, hour: 20, minute: 10))! -``` - -Text 视图 - -```swift -// 多种表现样式 -Text(aimDate, style: .relative) -Text(aimDate, style: .offset) -Text(aimDate, style: .timer) -Text(aimDate, style: .date) -Text(aimDate, style: .time) -``` - -两个时间间隔 - -```swift -let beginDate = Calendar.current.date(from: DateComponents(year: 2024, month: 6, day: 11, hour: 20, minute: 10))! -let endDate = Calendar.current.date(from: DateComponents(year: 2024, month: 6, day: 15, hour: 20, minute: 10))! - -Text(beginDate ... endDate) -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/TextEditor(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/TextEditor(ap).md" deleted file mode 100644 index cf986d79c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/TextEditor(ap).md" +++ /dev/null @@ -1,195 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/texteditor-ap01.png) - -对应的代码如下: - -```swift -import SwiftUI - -struct PlayTextEditorView: View { - // for TextEditor - @State private var txt: String = "一段可编辑文字...\n" - @State private var count: Int = 0 - - var body: some View { - - // 使用 SwiftUI 自带 TextEditor - TextEditor(text: $txt) - .font(.title) - .lineSpacing(10) - .disableAutocorrection(true) - .padding() - .onChange(of: txt) { newValue in - count = txt.count - } - Text("字数:\(count)") - .foregroundColor(.secondary) - .font(.footnote) - - // 包装的 NSTextView - HSplitView { - PNSTextView(text: .constant("左边写...\n"), onDidChange: { (s, i) in - print("Typing \(i) times.") - }) - .padding() - PNSTextView(text: .constant("右边写...\n")) - .padding() - } // end HSplitView - } // end body -} - -// MARK: - 自己包装 NSTextView -struct PNSTextView: NSViewRepresentable { - @Binding var text: String - var onBeginEditing: () -> Void = {} - var onCommit: () -> Void = {} - var onDidChange: (String, Int) -> Void = { _,_ in } - - // 返回要包装的 NSView - func makeNSView(context: Context) -> PNSTextConfiguredView { - let t = PNSTextConfiguredView(text: text) - t.delegate = context.coordinator - return t - } - - func updateNSView(_ view: PNSTextConfiguredView, context: Context) { - view.text = text - view.selectedRanges = context.coordinator.sRanges - } - - // 回调 - func makeCoordinator() -> TextViewDelegate { - TextViewDelegate(self) - } -} - -// 处理 delegate 回调 -extension PNSTextView { - class TextViewDelegate: NSObject, NSTextViewDelegate { - var tView: PNSTextView - var sRanges: [NSValue] = [] - var typeCount: Int = 0 - - init(_ v: PNSTextView) { - self.tView = v - } - // 开始编辑 - func textDidBeginEditing(_ notification: Notification) { - guard let textView = notification.object as? NSTextView else { - return - } - self.tView.text = textView.string - self.tView.onBeginEditing() - } - // 每次敲字 - func textDidChange(_ notification: Notification) { - guard let textView = notification.object as? NSTextView else { - return - } - typeCount += 1 - self.tView.text = textView.string - self.sRanges = textView.selectedRanges - self.tView.onDidChange(textView.string, typeCount) - } - // 提交 - func textDidEndEditing(_ notification: Notification) { - guard let textView = notification.object as? NSTextView else { - return - } - self.tView.text = textView.string - self.tView.onCommit() - } - } -} - -// 配置 NSTextView -final class PNSTextConfiguredView: NSView { - weak var delegate: NSTextViewDelegate? - - private lazy var tv: NSTextView = { - let contentSize = sv.contentSize - let textStorage = NSTextStorage() - - let layoutManager = NSLayoutManager() - textStorage.addLayoutManager(layoutManager) - - let textContainer = NSTextContainer(containerSize: sv.frame.size) - textContainer.widthTracksTextView = true - textContainer.containerSize = NSSize( - width: contentSize.width, - height: CGFloat.greatestFiniteMagnitude - ) - - layoutManager.addTextContainer(textContainer) - - let t = NSTextView(frame: .zero, textContainer: textContainer) - t.delegate = self.delegate - t.isEditable = true - t.allowsUndo = true - - t.font = .systemFont(ofSize: 24) - t.textColor = NSColor.labelColor - t.drawsBackground = true - t.backgroundColor = NSColor.textBackgroundColor - - t.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) - t.minSize = NSSize(width: 0, height: contentSize.height) - t.autoresizingMask = .width - - t.isHorizontallyResizable = false - t.isVerticallyResizable = true - - return t - }() - - private lazy var sv: NSScrollView = { - let s = NSScrollView() - s.drawsBackground = true - s.borderType = .noBorder - s.hasVerticalScroller = true - s.hasHorizontalRuler = false - s.translatesAutoresizingMaskIntoConstraints = false - s.autoresizingMask = [.width, .height] - return s - }() - - var text: String { - didSet { - tv.string = text - } - } - - var selectedRanges: [NSValue] = [] { - didSet { - guard selectedRanges.count > 0 else { - return - } - tv.selectedRanges = selectedRanges - } - } - - required init?(coder: NSCoder) { - fatalError("Error coder") - } - - init(text: String) { - self.text = text - super.init(frame: .zero) - } - - override func viewWillDraw() { - super.viewWillDraw() - sv.translatesAutoresizingMaskIntoConstraints = false - addSubview(sv) - NSLayoutConstraint.activate([ - sv.topAnchor.constraint(equalTo: topAnchor), - sv.trailingAnchor.constraint(equalTo: trailingAnchor), - sv.bottomAnchor.constraint(equalTo: bottomAnchor), - sv.leadingAnchor.constraint(equalTo: leadingAnchor) - ]) - sv.documentView = tv - } // end viewWillDraw - -} -``` - -SwiftUI 中用 NSView,可以通过 NSViewRepresentable 来包装视图,这个协议主要是实现 makeNSView、updateNSView 和 makeCoordinator 三个方法。makeNSView 要求返回需要包装的 NSView。每当 SwiftUI 的状态变化时触发 updateNSView 方法的调用。为了实现 NSView 里的 delegate 和 SwiftUI 通信,就要用 makeCoordinator 返回一个用于处理 delegate 的实例。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/TextField(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/TextField(ap).md" deleted file mode 100644 index a6869614b..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\233\276\346\226\207\347\273\204\344\273\266/TextField(ap).md" +++ /dev/null @@ -1,109 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/TextField-ap01.png) - -使用方法如下: - -```swift -struct PlayTextFieldView: View { - @State private var t = "Starming" - @State private var showT = "" - @State private var isEditing = false - var placeholder = "输入些文字..." - - @FocusState private var isFocus: Bool - - var body: some View { - VStack { - TextField(placeholder, text: $t) - - // 样式设置 - TextField(placeholder, text: $t) - .padding(10) - .textFieldStyle(.roundedBorder) // textFieldStyle 有三个预置值 automatic、plain 和 roundedBorder。 - .multilineTextAlignment(.leading) // 对齐方式 - .font(.system(size: 14, weight: .heavy, design: .rounded)) - .border(.teal, width: 4) - .background(.white) - .foregroundColor(.brown) - .textCase(.uppercase) - - // 多视图组合 - HStack { - Image(systemName: "lock.circle") - .foregroundColor(.gray).font(.headline) - TextField(placeholder, text: $t) - .textFieldStyle(.plain) - .submitLabel(.done) - .onSubmit { - showT = t - isFocus = true - } - .onChange(of: t) { newValue in - t = String(newValue.prefix(20)) // 限制字数 - } - Image(systemName: "eye.slash") - .foregroundColor(.gray) - .font(.headline) - } - .padding() - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(.gray, lineWidth: 1) - ) - .padding(.horizontal) - - Text(showT) - - - // 自定义 textFieldStyle 样式 - TextField(placeholder, text: $t) - .textFieldStyle(PClearTextStyle()) - .focused($isFocus) - } - .padding() - } // end body -} - -struct PClearTextStyle: TextFieldStyle { - @ViewBuilder - func _body(configuration: TextField<_Label>) -> some View { - let mirror = Mirror(reflecting: configuration) - let bindingText: Binding = mirror.descendant("_text") as! Binding - configuration - .overlay(alignment: .trailing) { - Button(action: { - bindingText.wrappedValue = "" - }, label: { - Image(systemName: "clear") - }) - } - - let text: String = mirror.descendant("_text", "_value") as! String - configuration - .padding() - .background( - RoundedRectangle(cornerRadius: 16) - .strokeBorder(text.count > 10 ? .pink : .gray, lineWidth: 4) - ) - } // end func -} -``` - -目前iOS 和 iPadOS上支持的键盘有: - -* asciiCapable:能显示标准 ASCII 字符的键盘 -* asciiCapableNumberPad:只输出 ASCII 数字的数字键盘 -* numberPad:用于输入 PIN 码的数字键盘 -* numbersAndPunctuation:数字和标点符号的键盘 -* decimalPad:带有数字和小数点的键盘 -* phonePad:电话中使用的键盘 -* namePhonePad:用于输入人名或电话号码的小键盘 -* URL:用于输入URL的键盘 -* emailAddress:用于输入电子邮件地址的键盘 -* twitter:用于Twitter文本输入的键盘,支持@和#字符简便输入 -* webSearch:用于网络搜索词和URL输入的键盘 - -可以通过 keyboardType 修改器来指定。 - -支持多行,使用 Axis.vertical 以允许多行。TextField 超过行限制可以变成滚动视图。 - -今年 TextField 可以嵌到 `.alert` 里了。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Advanced layout control(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Advanced layout control(ap).md" deleted file mode 100644 index b885139ef..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Advanced layout control(ap).md" +++ /dev/null @@ -1,202 +0,0 @@ -session [Compose custom layouts with SwiftUI](https://developer.apple.com/videos/play/wwdc2022-10056) - -提供了新的 Grid 视图来同时满足 VStack 和 HStack。还有一个更低级别 Layout 接口,可以完全控制构建应用所需的布局。另外还有 ViewThatFits 可以自动选择填充可用空间的方式。 - -Grid 示例代码如下: -```swift -Grid { - GridRow { - Text("One") - Text("One") - Text("One") - } - GridRow { - Text("Two") - Text("Two") - } - Divider() - GridRow { - Text("Three") - Text("Three") - .gridCellColumns(2) - } -} -``` - -`gridCellColumns()` modifier 可以让一个单元格跨多列。 - -ViewThatFits 的新视图,允许根据适合的大小放视图。ViewThatFits 会自动选择对于当前屏幕大小合适的子视图进行显示。Ryan Lintott 的[示例效果](https://twitter.com/ryanlintott/status/1534706352177700871) ,对应示例代码 [LayoutThatFits.swift](https://gist.github.com/ryanlintott/d03140dd155d0493a758dcd284e68eaa) 。 - -新的 Layout 协议可以观看 Swift Talk 第 308 期 [The Layout Protocol](https://talk.objc.io/episodes/S01E308-the-layout-protocol) 。 - -通过符合 Layout 协议,我们可以自定义一个自定义的布局容器,直接参与 SwiftUI 的布局过程。新的 ProposedViewSize 结构,它是容器视图提供的大小。 `Layout.Subviews` 是布局视图的子视图代理集合,我们可以在其中为每个子视图请求各种布局属性。 -```swift -public protocol Layout: Animatable { - static var layoutProperties: LayoutProperties { get } - associatedtype Cache = Void - typealias Subviews = LayoutSubviews - - func updateCache(_ cache: inout Self.Cache, subviews: Self.Subviews) - - func spacing(subviews: Self.Subviews, cache: inout Self.Cache) -> ViewSpacing - - /// We return our view size here, use the passed parameters for computing the - /// layout. - func sizeThatFits( - proposal: ProposedViewSize, - subviews: Self.Subviews, - cache: inout Self.Cache // 👈🏻 use this for calculated data shared among Layout methods - ) -> CGSize - - /// Use this to tell your subviews where to appear. - func placeSubviews( - in bounds: CGRect, // 👈🏻 region where we need to place our subviews into, origin might not be .zero - proposal: ProposedViewSize, - subviews: Self.Subviews, - cache: inout Self.Cache - ) - - // ... there are more a couple more optional methods -} -``` - -下面例子是一个自定义的水平 stack 视图,为其所有子视图提供其最大子视图的宽度: -```swift -struct MyEqualWidthHStack: Layout { - /// Returns a size that the layout container needs to arrange its subviews. - /// - Tag: sizeThatFitsHorizontal - func sizeThatFits( - proposal: ProposedViewSize, - subviews: Subviews, - cache: inout Void - ) -> CGSize { - guard !subviews.isEmpty else { return .zero } - - let maxSize = maxSize(subviews: subviews) - let spacing = spacing(subviews: subviews) - let totalSpacing = spacing.reduce(0) { $0 + $1 } - - return CGSize( - width: maxSize.width * CGFloat(subviews.count) + totalSpacing, - height: maxSize.height) - } - - /// Places the stack's subviews. - /// - Tag: placeSubviewsHorizontal - func placeSubviews( - in bounds: CGRect, - proposal: ProposedViewSize, - subviews: Subviews, - cache: inout Void - ) { - guard !subviews.isEmpty else { return } - - let maxSize = maxSize(subviews: subviews) - let spacing = spacing(subviews: subviews) - - let placementProposal = ProposedViewSize(width: maxSize.width, height: maxSize.height) - var nextX = bounds.minX + maxSize.width / 2 - - for index in subviews.indices { - subviews[index].place( - at: CGPoint(x: nextX, y: bounds.midY), - anchor: .center, - proposal: placementProposal) - nextX += maxSize.width + spacing[index] - } - } - - /// Finds the largest ideal size of the subviews. - private func maxSize(subviews: Subviews) -> CGSize { - let subviewSizes = subviews.map { $0.sizeThatFits(.unspecified) } - let maxSize: CGSize = subviewSizes.reduce(.zero) { currentMax, subviewSize in - CGSize( - width: max(currentMax.width, subviewSize.width), - height: max(currentMax.height, subviewSize.height)) - } - - return maxSize - } - - /// Gets an array of preferred spacing sizes between subviews in the - /// horizontal dimension. - private func spacing(subviews: Subviews) -> [CGFloat] { - subviews.indices.map { index in - guard index < subviews.count - 1 else { return 0 } - return subviews[index].spacing.distance( - to: subviews[index + 1].spacing, - along: .horizontal) - } - } -} -``` - -自定义 layout 只能访问子视图代理 `Layout.Subviews` ,而不是视图或数据模型。我们可以通过 LayoutValueKey 在每个子视图上存储自定义值,通过 `layoutValue(key:value:)` modifier 设置。 -```swift -private struct Rank: LayoutValueKey { - static let defaultValue: Int = 1 -} - -extension View { - func rank(_ value: Int) -> some View { // 👈🏻 convenience method - layoutValue(key: Rank.self, value: value) // 👈🏻 the new modifier - } -} -``` - -然后,我们就可以通过 Layout 方法中的 `Layout.Subviews` 代理读取自定义 `LayoutValueKey` 值: -```swift -func placeSubviews( - in bounds: CGRect, - proposal: ProposedViewSize, - subviews: Subviews, - cache: inout Void -) { - let ranks = subviews.map { subview in - subview[Rank.self] // 👈🏻 - } - - // ... -} -``` - -要在布局之间变化使用动画,需要用 AnyLayout,代码示例如下: -```swift -struct PAnyLayout: View { - @State private var isVertical = false - var body: some View { - let layout = isVertical ? AnyLayout(VStack()) : AnyLayout(HStack()) - layout { - Image(systemName: "star").foregroundColor(.yellow) - Text("Starming.com") - Text("戴铭") - } - Button("Click") { - withAnimation { - isVertical.toggle() - } - } // end button - } // end body -} -``` - -同时 Text 和图片也支持了样式布局变化,代码示例如下: -```swift -struct PTextTransitionsView: View { - @State private var expandMessage = true - private let mintWithShadow: AnyShapeStyle = AnyShapeStyle(Color.mint.shadow(.drop(radius: 2))) - private let primaryWithoutShadow: AnyShapeStyle = AnyShapeStyle(Color.primary.shadow(.drop(radius: 0))) - - var body: some View { - Text("Dai Ming Swift Pamphlet") - .font(expandMessage ? .largeTitle.weight(.heavy) : .body) - .foregroundStyle(expandMessage ? mintWithShadow : primaryWithoutShadow) - .onTapGesture { withAnimation { expandMessage.toggle() }} - .frame(maxWidth: expandMessage ? 150 : 250) - .drawingGroup() - .padding(20) - .background(.cyan.opacity(0.3), in: RoundedRectangle(cornerRadius: 6)) - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/ContentUnavailableView(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/ContentUnavailableView(ap).md" deleted file mode 100644 index b42eae679..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/ContentUnavailableView(ap).md" +++ /dev/null @@ -1,56 +0,0 @@ - -基本用法 - -```swift -struct ArchivedInfosView: View { - @Environment(\.modelContext) var modelContext - @Query var infos: [IOInfo] - ... - - var body: some View { - List(selection: $selectInfo) { - ForEach(infos) { info in - ... - } - } - .overlay { - if infos.isEmpty { - ContentUnavailableView { - Label("无归档", systemImage: "archivebox") - } description: { - Text("点击下方按钮添加一个归档资料") - } actions: { - Button("新增") { - addInfo() - } - } - } - } - } - ... -} -``` - -搜索 -```swift -struct ContentView: View { - @Bindable var vm: VModel - ... - - var body: some View { - NavigationStack { - List(vm.items, id: \.self) { item in - ... - } - .navigationTitle("Products") - .overlay { - if vm.items.isEmpty { - ContentUnavailableView.search(text: vm.query) - } - } - .searchable(text: $vm.query) - } - ... - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/ControlGroup(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/ControlGroup(ap).md" deleted file mode 100644 index adecbe92f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/ControlGroup(ap).md" +++ /dev/null @@ -1,21 +0,0 @@ -```swift -struct PlayControlGroupView: View { - var body: some View { - ControlGroup { - Button { - print("plus") - } label: { - Image(systemName: "plus") - } - - Button { - print("minus") - } label: { - Image(systemName: "minus") - } - } - .padding() - .controlGroupStyle(.automatic) // .automatic 是默认样式,还有 .navigation - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/GroupBox(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/GroupBox(ap).md" deleted file mode 100644 index 34b617c98..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/GroupBox(ap).md" +++ /dev/null @@ -1,137 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/groupbox-ap01.png) - -```swift -struct PlayGroupBoxView: View { - var body: some View { - GroupBox { - Text("这是 GroupBox 的内容") - } label: { - Label("标题一", systemImage: "t.square.fill") - } - .padding() - - GroupBox { - Text("还是 GroupBox 的内容") - } label: { - Label("标题二", systemImage: "t.square.fill") - } - .padding() - .groupBoxStyle(PCGroupBoxStyle()) - - } -} - -struct PCGroupBoxStyle: GroupBoxStyle { - func makeBody(configuration: Configuration) -> some View { - VStack(alignment: .leading) { - configuration.label - .font(.title) - configuration.content - } - .padding() - .background(.pink) - .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous)) - } -} -``` - -叠加 GroupBox 颜色会有区分 - -```swift -GroupBox { - Text("电视剧名称: 人民的名义") - - GroupBox { - Text("播放时间: 每周一至周五") - } -} -``` - -最后,您还可以 `GroupBox` 使用 `Label` .将 `Label` 定位为 `GroupBox` 容器的标题。 -```swift -GroupBox(label: Label("电视剧", systemImage: "tv")) { - HStack { - Text("播放时间: 每周一至周五") - .padding() - Spacer() - } -} -``` - -GroupBox 也可以用于创建自定义的按钮组,如下所示: - -```swift -struct TVShowCardView: View { - var body: some View { - VStack(alignment: .leading) { - // The header of the card - // - Photo, Show Name and Genre - HStack { - Circle() - .frame(width: 40, height: 40) - .foregroundColor(.gray) - VStack(alignment: .leading, spacing: 3) { - Text("权力的游戏") - .font(.headline) - .fontWeight(.semibold) - Text("奇幻剧") - .font(.caption) - } - Spacer() - } - - Divider() - .foregroundColor(Color(uiColor: UIColor.systemGray6)) - .padding([.top, .bottom], 8) - - // The description of the show in a few lines - Text("《权力的游戏》是一部改编自乔治·马丁的奇幻小说系列《冰与火之歌》的电视剧。") - .font(.body) - - // Buttons to watch, share or save the show - HStack { - actionGroupBox(imageName: "play.rectangle", actionName: "观看", action: { print("Watching...") }) - actionGroupBox(imageName: "square.and.arrow.up", actionName: "分享", action: { print("Sharing...") }) - actionGroupBox(imageName: "bookmark", actionName: "保存", action: { print("Saving...") }) - } - } - .padding() - .background(Color.white) - .cornerRadius(10) - } - - // A function to create a GroupBox for an action - func actionGroupBox(imageName: String, actionName: String, action: @escaping () -> Void) -> some View { - GroupBox { - VStack(spacing: 5) { - Image(systemName: imageName) - .font(.headline) - Text(actionName) - .font(.caption) - } - .foregroundColor(.red) - .frame(maxWidth: .infinity) - }.onTapGesture { - action() - } - } -} - -struct ContentView: View { - var body: some View { - NavigationView { - ScrollView { - TVShowCardView() - - Spacer() - } - .padding() - .background(Color(UIColor.systemGray6)) - .navigationTitle("电视剧") - .shadow(color: Color(.sRGB, red: 0, green: 0, blue: 0, opacity: 0.25), radius: 10, x: 0, y: 0) - } - } -} - -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/Inspectors\345\217\263\344\276\247\345\244\232\345\207\272\344\270\200\346\240\217(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/Inspectors\345\217\263\344\276\247\345\244\232\345\207\272\344\270\200\346\240\217(ap).md" deleted file mode 100644 index a87fd244a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/Inspectors\345\217\263\344\276\247\345\244\232\345\207\272\344\270\200\346\240\217(ap).md" +++ /dev/null @@ -1,68 +0,0 @@ - -Inspector 的示例 - -```swift -struct Book: Identifiable { - var id = UUID() - var title: String - var author: String - var description: String -} - -struct ContentView: View { - @State var books: [Book] = [ - Book(title: "Book 1", author: "Author 1", description: "Description 1"), - Book(title: "Book 2", author: "Author 2", description: "Description 2"), - Book(title: "Book 3", author: "Author 3", description: "Description 3") - ] - @State var selectedBook: Book? - @State var showInspector: Bool = false - @State var splitVisibility: NavigationSplitViewVisibility = .all - - var body: some View { - NavigationSplitView(columnVisibility: $splitVisibility, sidebar: { - List(books) { book in - Button(action: { selectedBook = book }) { - Text(book.title) - } - } - }, content: { - if let book = selectedBook { - Text("Author: \(book.author)") - } else { - Text("Select a Book") - } - }, detail: { - Button("Inspector 开关") { - showInspector.toggle() - } - if let book = selectedBook { - Text(book.description) - } else { - Text("Book details will appear here") - } - }) - .inspector(isPresented: $showInspector) { - if let book = selectedBook { - InspectorView(book: book) - } - } - } -} - -struct InspectorView: View { - var book: Book - - var body: some View { - VStack { - Text(book.title).font(.largeTitle) - Text("Author: \(book.author)").font(.title) - Text(book.description).padding() - } - .inspectorColumnWidth(200) - .presentationDetents([.medium, .large]) - } -} -``` - -它显示了一个图书列表。当用户选择一个图书时,会显示 InspectorView,这是辅助视图,它显示了图书的详细信息。inspector 方法用于显示和隐藏 InspectorView,inspectorColumnWidth 方法用于设置辅助视图的宽度,presentationDetents 方法用于设置辅助视图的大小。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/Navigation(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/Navigation(ap).md" deleted file mode 100644 index a23e8aaed..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/Navigation(ap).md" +++ /dev/null @@ -1,266 +0,0 @@ -控制导航启动状态、管理 size class 之间的 transition 和响应 deep link。 - -Navigation bar 有新的默认行为,如果没有提供标题,导航栏默认为 inline title 显示模式。使用 `navigationBarTitleDisplayMode(_:)` 改变显示模式。如果 navigation bar 没有标题、工具栏项或搜索内容,它就会自动隐藏。使用 `.toolbar(.visible)` modifier 显示一个空 navigation bar。 - -参考: -- [Migrating to New Navigation Types](https://developer.apple.com/documentation/swiftui/migrating-to-new-navigation-types?changes=latest_minor) 官方迁移指南 -- [NavigationStack](https://developer.apple.com/documentation/swiftui/navigationstack?changes=latest_minor) -- [NavigationSplitView](https://developer.apple.com/documentation/swiftui/navigationsplitview) -- [The SwiftUI cookbook for navigation](https://developer.apple.com/videos/play/wwdc2022/10054/) - -NavigationStack 的示例: -```swift -struct PNavigationStack: View { - @State private var a = [1, 3, 9] // 深层链接 - var body: some View { - NavigationStack(path: $a) { - List(1..<10) { i in - NavigationLink(value: i) { - Label("第 \(i) 行", systemImage: "\(i).circle") - } - } - .navigationDestination(for: Int.self) { i in - Text("第 \(i) 行内容") - } - .navigationTitle("NavigationStack Demo") - } - } -} -``` - -这里的 path 设置了 stack 的深度路径。 - -NavigationSplitView 两栏的例子: -```swift -struct PNavigationSplitViewTwoColumn: View { - @State private var a = ["one", "two", "three"] - @State private var choice: String? - - var body: some View { - NavigationSplitView { - List(a, id: \.self, selection: $choice, rowContent: Text.init) - } detail: { - Text(choice ?? "选一个") - } - } -} -``` - -NavigationSplitView 三栏的例子: -```swift -struct PNavigationSplitViewThreeColumn: View { - struct Group: Identifiable, Hashable { - let id = UUID() - var title: String - var subs: [String] - } - - @State private var gps = [ - Group(title: "One", subs: ["o1", "o2", "o3"]), - Group(title: "Two", subs: ["t1", "t2", "t3"]) - ] - - @State private var choiceGroup: Group? - @State private var choiceSub: String? - - @State private var cv = NavigationSplitViewVisibility.automatic - - var body: some View { - NavigationSplitView(columnVisibility: $cv) { - List(gps, selection: $choiceGroup) { g in - Text(g.title).tag(g) - } - .navigationSplitViewColumnWidth(250) - } content: { - List(choiceGroup?.subs ?? [], id: \.self, selection: $choiceSub) { s in - Text(s) - } - } detail: { - Text(choiceSub ?? "选一个") - Button("点击") { - cv = .all - } - } - .navigationSplitViewStyle(.prominentDetail) - } -} -``` - -`navigationSplitViewColumnWidth() ` 是用来自定义宽的,`navigationSplitViewStyle` 设置为 `.prominentDetail` 是让 detail 的视图尽量保持其大小。 - -SwiftUI 新加了个[功能](https://developer.apple.com/documentation/swiftui/presentedwindowcontent/toolbar(_:in:))可以配置是否隐藏 Tabbar,这样在从主页进入下一级时就可以选择不显示底部标签栏了,示例代码如下: -```swift -ContentView().toolbar(.hidden, in: .tabBar) -``` - -相比较以前 NavigationView 增强的是 destination 可以根据值的不同类型展示不同的目的页面,示例代码如下: -```swift -struct PNavigationStackDestination: View { - var body: some View { - NavigationStack { - List { - NavigationLink(value: "字符串") { - Text("字符串") - } - NavigationLink(value: Color.red) { - Text("红色") - } - } - .navigationTitle("不同类型 Destination") - .navigationDestination(for: Color.self) { c in - c.clipShape(Circle()) - } - .navigationDestination(for: String.self) { s in - Text("\(s) 的 detail") - } - } - } -} -``` - -对 toolbar 的自定义,示例如下: -```swift -.toolbar(id: "toolbar") { - ToolbarItem(id: "new", placement: .secondaryAction) { - Button(action: {}) { - Label("New Invitation", systemImage: "envelope") - } - } -} -.toolbarRole(.editor) -``` - -以下是废弃的 NavigationView 的用法。 - -![](https://ming1016.github.io/qdimg/240505/navigation-ap01.jpeg) - -对应代码如下: - -```swift -struct PlayNavigationView: View { - let lData = 1...10 - var body: some View { - NavigationView { - ZStack { - LinearGradient(colors: [.pink, .orange], startPoint: .topLeading, endPoint: .bottomTrailing) - .ignoresSafeArea() - - List(lData, id: \.self) { i in - NavigationLink { - PNavDetailView(contentStr: "\(i)") - } label: { - Text("\(i)") - } - } - } - - ZStack { - LinearGradient(colors: [.mint, .yellow], startPoint: .topLeading, endPoint: .bottomTrailing) - .ignoresSafeArea() - - VStack { - Text("一个 NavigationView 的示例") - .bold() - .font(.largeTitle) - .shadow(color: .white, radius: 9, x: 0, y: 0) - .scaleEffect(2) - } - } - .safeAreaInset(edge: .bottom) { - HStack { - Button("bottom1") {} - .font(.headline) - Button("bottom2") {} - Button("bottom3") {} - Spacer() - } - .padding(5) - .background(LinearGradient(colors: [.purple, .blue], startPoint: .topLeading, endPoint: .bottomTrailing)) - } - } - .foregroundColor(.white) - .navigationTitle("数字列表") - .toolbar { - // placement 共有 keyboard、destructiveAction、cancellationAction、confirmationAction、status、primaryAction、navigation、principal、automatic 这些 - ToolbarItem(placement: .primaryAction) { - Button("primaryAction") {} - .background(.ultraThinMaterial) - .font(.headline) - } - // 通过 ToolbarItemGroup 可以简化相同位置 ToolbarItem 的编写。 - ToolbarItemGroup(placement: .navigation) { - Button("返回") {} - Button("前进") {} - } - PCToolbar(doDestruct: { - print("删除了") - }, doCancel: { - print("取消了") - }, doConfirm: { - print("确认了") - }) - ToolbarItem(placement: .status) { - Button("status") {} - } - ToolbarItem(placement: .principal) { - Button("principal") { - - } - } - ToolbarItem(placement: .keyboard) { - Button("Touch Bar Button") {} - } - } // end toolbar - } -} - -// MARK: - NavigationView 的目的页面 -struct PNavDetailView: View { - @Environment(\.presentationMode) var pMode: Binding - var contentStr: String - var body: some View { - ZStack { - LinearGradient(colors: [.purple, .blue], startPoint: .topLeading, endPoint: .bottomTrailing) - .ignoresSafeArea() - VStack { - Text(contentStr) - Button("返回") { - pMode.wrappedValue.dismiss() - } - } - } // end ZStack - } // end body -} - -// MARK: - 自定义 toolbar -// 通过 ToolbarContent 创建可重复使用的 toolbar 组 -struct PCToolbar: ToolbarContent { - let doDestruct: () -> Void - let doCancel: () -> Void - let doConfirm: () -> Void - - var body: some ToolbarContent { - ToolbarItem(placement: .destructiveAction) { - Button("删除", action: doDestruct) - } - ToolbarItem(placement: .cancellationAction) { - Button("取消", action: doCancel) - } - ToolbarItem(placement: .confirmationAction) { - Button("确定", action: doConfirm) - } - } -} -``` - -toolbar 的位置设置可选项如下: - -* primaryAction:放置到最主要位置,macOS 就是放在 toolbar 的最左边 -* automatic:根据平台不同放到默认位置 -* confirmationAction:一些确定的动作 -* cancellationAction:取消动作 -* destructiveAction:删除的动作 -* status:状态变化,比如检查更新等动作 -* navigation:导航动作,比如浏览器的前进后退 -* principal:突出的位置,iOS 和 macOS 会出现在中间的位置 -* keyboard:macOS 会出现在 Touch Bar 里。iOS 会出现在弹出的虚拟键盘上。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationPath(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationPath(ap).md" deleted file mode 100644 index a784fd6bd..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationPath(ap).md" +++ /dev/null @@ -1,80 +0,0 @@ - -`NavigationPath` 是一个用于管理 SwiftUI 中导航路径的工具。它可以帮助你在 SwiftUI 中实现更复杂的导航逻辑。 - -在 SwiftUI 中,我们通常使用 `NavigationLink` 来实现导航。然而,`NavigationLink` 只能实现简单的前进导航,如果你需要实现更复杂的导航逻辑,例如后退、跳转到任意页面等,你就需要使用 `NavigationPath`。 - -`NavigationPath` 的工作原理是,它维护了一个路径数组,每个元素代表一个页面。当你需要导航到一个新的页面时,你只需要将这个页面添加到路径数组中。当你需要后退时,你只需要从路径数组中移除最后一个元素。这样,你就可以实现任意复杂的导航逻辑。 - -看个例子 - -假设我们有一个 TVShow 结构体,它包含电视剧的名字。当用户点击一个电视剧的名字时,他们会被导航到这个电视剧的详细信息页面。 - -```swift -struct ContentView: View { - @State private var path = NavigationPath() - @State private var tvShows = [ TVShow(name: "Game of Thrones"), TVShow(name: "Breaking Bad"), TVShow(name: "The Witcher") ] - - var body: some View { - NavigationStack(path: $path) { - List { - Text("Select a TV show to get started.") - .font(.subheadline.weight(.semibold)) - ForEach(tvShows, id: \.name) { show in - NavigationLink(value: show, label: { - Text(show.name) - .font(.subheadline.weight(.medium)) - }) - } - Button(action: showFriends) { - Text("This isn't navigation") - } - } - .navigationDestination(for: TVShow.self, destination: { show in - TVShowView(onSelectReset: { popToRoot() }, show: show, otherShows: tvShows) - }) - .navigationTitle(Text("Select your show")) - } - .onChange(of: path.count) { oldValue, newValue in - print(newValue) - } - } - - func showFriends() { - let show = TVShow(name: "Friends") - path.append(show) - } - - func popToRoot() { - path.removeLast(path.count) - } -} - -struct TVShowView: View { - var onSelectReset: () -> Void - var show: TVShow - var otherShows: [TVShow] - - var body: some View { - VStack { - Text(show.name) - .font(.title) - .padding(.bottom) - Button(action: onSelectReset) { - Text("Reset Selection") - } - List(otherShows, id: \.name) { otherShow in - Text(otherShow.name) - } - } - .padding() - } -} - -struct TVShow: Hashable { - let name: String - let premiereDate: Date = Date.now - var description: String = "detail" -} -``` - -代码中,`NavigationPath` 被用作一个 `@State` 变量,这意味着它会自动响应变化,并更新视图。当你修改 `NavigationPath` 中的路径数组时,视图会自动更新,显示新的页面。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationSplitView(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationSplitView(ap).md" deleted file mode 100644 index 0e5b17bc8..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationSplitView(ap).md" +++ /dev/null @@ -1,50 +0,0 @@ - -以下是一个基于 NavigationSplitView 的三栏视图的示例。这个示例包含了一个主视图,一个次级视图和一个详细视图。 - -```swift -struct ContentView: View { - @State var books: [Book] = [ - Book(title: "Book 1", author: "Author 1", description: "Description 1"), - Book(title: "Book 2", author: "Author 2", description: "Description 2"), - Book(title: "Book 3", author: "Author 3", description: "Description 3") - ] - @State var selectedBook: Book? - @State var splitVisibility: NavigationSplitViewVisibility = .all - - var body: some View { - NavigationSplitView(columnVisibility: $splitVisibility, sidebar: { - List(books) { book in - Button(action: { selectedBook = book }) { - Text(book.title) - } - } - }, content: { - if let book = selectedBook { - Text("Author: \(book.author)") - } else { - Text("Select a Book") - } - }, detail: { - if let book = selectedBook { - Text(book.description) - } else { - Text("Book details will appear here") - } - }) - .onChange(of: selectedBook) { oldValue, newValue in - //... - } - } -} - -struct Book: Identifiable, Equatable { - var id = UUID() - var title: String - var author: String - var description: String -} -``` - -示例中,`sidebar` 是主视图,它显示了一个图书列表。当用户选择一个图书时,`content` 视图会显示图书的作者,`detail` 视图会显示图书的详细信息。`NavigationSplitView` 会根据 `splitVisibility` 的值来决定显示哪些视图。 - -使用 `.toolbar(removing: .sidebarToggle)` 可以移除边栏隐藏的按钮。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationStack(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationStack(ap).md" deleted file mode 100644 index bb362abd6..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/NavigationStack(ap).md" +++ /dev/null @@ -1,115 +0,0 @@ - -## 使用示例 - -假设我们有一个 TVShow 结构体和一个 Book 结构体,它们分别包含电视剧和书籍的名字。当用户点击一个电视剧或书籍的名字时,他们会被导航到相应的详细信息页面。 - -以下是一个例子: - -```swift -struct TVShow: Hashable { - let name: String -} - -struct Book: Hashable { - let name: String -} - -struct ContentView: View { - @State var tvShows = [TVShow(name: "Game of Thrones"), TVShow(name: "Breaking Bad")] - @State var books = [Book(name: "1984"), Book(name: "To Kill a Mockingbird")] - - var body: some View { - NavigationStack { - List { - Section(header: Text("Best TV Shows")) { - ForEach(tvShows, id: \.name) { show in - NavigationLink(value: show, label: { - Text(show.name) - }) - } - } - Section(header: Text("Books")) { - ForEach(books, id: \.name) { book in - NavigationLink(value: book, label: { - Text(book.name) - }) - } - } - } - .navigationDestination(for: TVShow.self) { show in - TVShowView(show: show) - } - .navigationDestination(for: Book.self) { book in - BookView(book: book) - } - .navigationTitle(Text("Media")) - } - } -} - -struct TVShowView: View { - let show: TVShow - - var body: some View { - Text("Details for \(show.name)") - } -} - -struct BookView: View { - let book: Book - - var body: some View { - Text("Details for \(book.name)") - } -} -``` - -## 全局路由 - -先写个路由的枚举 - -```swift -enum Route: Hashable { - case all - case add(Book) - case detail(Book) -} - -struct Book { - let name: String - let des: String -} -``` - -在 App 中设置好全局路由 - -```swift -@main -struct LearnNavApp: App { - var body: some Scene { - WindowGroup { - NavigationStack { - ContentView() - .navigationDestination(for: Route.self) { route in - switch route { - case .all: - Text("显示所有图书") - case .create(let book): - Text("添加书 \(book.name)") - case .detail(let book): - Text("详细 \(book.des)") - } - } - } - - } - } -} -``` - -所有视图都可调用,调用方式如下: - -```swift -NavigationLink("查看书籍详细说明", value: Route.detail(Book(name: "1984", des: "1984 Detail"))) -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/\345\257\274\350\210\252\347\212\266\346\200\201\344\277\235\345\255\230\345\222\214\350\277\230\345\216\237(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/\345\257\274\350\210\252\347\212\266\346\200\201\344\277\235\345\255\230\345\222\214\350\277\230\345\216\237(ap).md" deleted file mode 100644 index 3f961b9b8..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/\345\257\274\350\210\252\347\212\266\346\200\201\344\277\235\345\255\230\345\222\214\350\277\230\345\216\237(ap).md" +++ /dev/null @@ -1,104 +0,0 @@ - -通过 SceneStorage 保存导航路径,程序终止时会持久化存储路径,重启时恢复路径。 - -```swift -protocol URLProcessor { - associatedtype RouteType: Hashable - func process(_ url: URL, mutating: inout [RouteType]) -} - -protocol UserActivityProcessor { - associatedtype RouteType: Hashable - func process(_ activity: NSUserActivity, mutating: inout [RouteType]) -} - -@Observable -@MainActor final class RouteManager { - var navigationPath: [RouteType] = [] - - private let jsonDecoder = JSONDecoder() - private let jsonEncoder = JSONEncoder() - private let urlProcessor: any URLProcessor - private let activityProcessor: any UserActivityProcessor - - init( - urlProcessor: some URLProcessor, - activityProcessor: some UserActivityProcessor - ) { - self.urlProcessor = urlProcessor - self.activityProcessor = activityProcessor - } - - func process(_ activity: NSUserActivity) { - activityProcessor.process(activity, mutating: &navigationPath) - } - - func process(_ url: URL) { - urlProcessor.process(url, mutating: &navigationPath) - } -} - -extension RouteManager where RouteType: Codable { - func toData() -> Data? { - try? jsonEncoder.encode(navigationPath) - } - - func restore(from data: Data) { - do { - navigationPath = try jsonDecoder.decode([RouteType].self, from: data) - } catch { - navigationPath = [] - } - } -} - -``` - -这段代码定义了一个名为 `RouteManager` 的类,它用于处理和管理导航路径。这个类使用了 SwiftUI 的 `@MainActor` 和 `@Observable` 属性包装器,以确保它的操作在主线程上执行,并且当 `navigationPath` 发生变化时,会自动更新相关的 UI。 - -`RouteManager` 类有两个协议类型的属性:`urlProcessor` 和 `activityProcessor`。这两个属性分别用于处理 URL 和用户活动(`NSUserActivity`)。这两个处理器的任务是根据给定的 URL 或用户活动,更新 `navigationPath`。 - -`RouteManager` 类还有两个方法:`process(_ activity: NSUserActivity)` 和 `process(_ url: URL)`。这两个方法分别用于处理用户活动和 URL。处理的方式是调用相应的处理器的 `process` 方法。 - -此外,`RouteManager` 类还有一个扩展,这个扩展只适用于 `RouteType` 是 `Codable` 的情况。这个扩展提供了两个方法:`toData()` 和 `restore(from data: Data)`。`toData()` 方法将 `navigationPath` 转换为 `Data`,`restore(from data: Data)` 方法则将 `Data` 转换回 `navigationPath`。这两个方法可以用于将 `navigationPath` 保存到磁盘,并在需要时从磁盘恢复。 - - -```swift -struct MainView: View { - @SceneStorage("navigationState") private var navigationData: Data? - @State private var dataStore = DataStore() - @State private var routeManager = RouteManager( - urlProcessor: SomeURLProcessor(), - activityProcessor: SomeUserActivityProcessor() - ) - - var body: some View { - NavigationStack(path: $routeManager.navigationPath) { - SomeView(categories: dataStore.categories) - .task { await dataStore.fetch() } - .navigationDestination(for: Route.self) { route in - // ... - } - .onOpenURL { routeManager.process($0) } - } - .task { - if let navigationData = navigationData { - routeManager.restore(from: navigationData) - } - - for await _ in routeManager.$navigationPath.values { - navigationData = routeManager.toData() - } - } - } -} - -``` - -`@SceneStorage("navigationState")` 是用来保存和恢复导航状态的。当应用程序被挂起时,它会自动将 `navigationData` 保存到磁盘,当应用程序重新启动时,它会自动从磁盘恢复 `navigationData`。 - -`@State private var dataStore = DataStore()` 和 `@State private var routeManager = RouteManager(...)` 是用来存储数据和路由管理器的。`DataStore` 是用来获取和存储数据的,`RouteManager` 是用来处理和管理导航路径的。 - -`body` 属性定义了视图的内容。它首先创建了一个 `NavigationStack`,然后在这个 `NavigationStack` 中创建了一个 `SomeView`。`SomeView` 使用了 `dataStore.categories` 作为它的参数,并且在被创建后立即执行 `dataStore.fetch()` 来获取数据。 - -`body` 属性还定义了一个任务,这个任务在视图被创建后立即执行。这个任务首先检查 `navigationData` 是否存在,如果存在,就使用 `routeManager.restore(from: navigationData)` 来恢复导航路径。然后,它监听 `routeManager.$navigationPath.values`,每当 `navigationPath` 发生变化时,就使用 `routeManager.toData()` 来将 `navigationPath` 转换为 `Data`,并将结果保存到 `navigationData` 中。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/\350\207\252\345\256\232\344\271\211\345\257\274\350\210\252\346\240\217(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/\350\207\252\345\256\232\344\271\211\345\257\274\350\210\252\346\240\217(ap).md" deleted file mode 100644 index f2f18dbd3..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Navigation\345\257\274\350\210\252/\350\207\252\345\256\232\344\271\211\345\257\274\350\210\252\346\240\217(ap).md" +++ /dev/null @@ -1,52 +0,0 @@ - - - -## 交互样式 - -使用 `navigationSplitViewStyle(_:)` 修饰符 - -## 改变标签栏背景色 - -```swift -.toolbarBackground(.yellow.gradient, for: .automatic) -.toolbarBackground(.visible, for: .automatic) -``` - -## 列宽 - - `navigationSplitViewColumnWidth(_:)` 修饰符用于指定列宽。 - - 设置列的最小、最大和理想大小,使用 `navigationSplitViewColumnWidth(min:ideal:max:)`。可以修饰于不同的列上。 - -## 自定返回按钮 - -先通过修饰符隐藏系统返回按钮 `.navigationBarBackButtonHidden(true)`。然后通过 `ToolbarItem(placement: .navigationBarLeading)` 来添加自定义的返回按钮。 - -```swift -struct BookDetailView: View { - var book: Book - @Binding var isDetailShown: Bool - - var body: some View { - VStack { - Text(book.title).font(.largeTitle) - Text("Author: \(book.author)").font(.title) - Text(book.description).padding() - } - .navigationBarBackButtonHidden(true) - .navigationTitle(book.title) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button { - isDetailShown = false - } label: { - HStack { - Image(systemName: "chevron.backward") - Text("Back to Books") - } - } - } - } - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Stack(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Stack(ap).md" deleted file mode 100644 index 914fa334f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/Stack(ap).md" +++ /dev/null @@ -1,38 +0,0 @@ -Stack View 有 VStack、HStack 和 ZStack - -![](https://ming1016.github.io/qdimg/240505/stack-ap01.jpeg) - -```swift -struct PlayStackView: View { - var body: some View { - // 默认是 VStack 竖排 - - // 横排 - HStack { - Text("左") - Spacer() - Text("右") - } - .padding() - - // Z 轴排 - ZStack(alignment: .top) { - Image("logo") - Text("戴铭的开发小册子") - .font(.title) - .bold() - .foregroundColor(.white) - .shadow(color: .black, radius: 1, x: 0, y: 2) - .padding() - } - - Color.cyan - .cornerRadius(10) - .frame(width: 100, height: 100) - .overlay( - Text("一段文字") - ) - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/TabView(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/TabView(ap).md" deleted file mode 100644 index 96b6ba897..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/TabView(ap).md" +++ /dev/null @@ -1,194 +0,0 @@ - -## 基本用法 - -```swift -struct PlayTabView: View { - @State private var selection = 0 - - var body: some View { - ZStack(alignment: .bottom) { - TabView(selection: $selection) { - Text("one") - .tabItem { - Text("首页") - .hidden() - } - .tag(0) - Text("two") - .tabItem { - Text("二栏") - } - .tag(1) - Text("three") - .tabItem { - Text("三栏") - } - .tag(2) - Text("four") - .tag(3) - Text("five") - .tag(4) - Text("six") - .tag(5) - Text("seven") - .tag(6) - Text("eight") - .tag(7) - Text("nine") - .tag(8) - Text("ten") - .tag(9) - } // end TabView - - - HStack { - Button("上一页") { - if selection > 0 { - selection -= 1 - } - } - .keyboardShortcut(.cancelAction) - Button("下一页") { - if selection < 9 { - selection += 1 - } - } - .keyboardShortcut(.defaultAction) - } // end HStack - .padding() - } - } -} -``` - -.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) 可以实现 UIPageViewController 的效果,如果要给小白点加上背景,可以多添加一个 .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)) 修改器。 - -## 添加提醒 - -```swift -struct ContentView: View { - @State private var bookVm: BooksViewModel - - init() { - bookVm = BooksViewModel() - } - - var body: some View { - TabView { - BookListView(bookVm: bookVm) - .tabItem { - Image(systemName: "list.bullet.rectangle.fill") - Text("Book List") - } - SelectedBooksView(bookVm: bookVm) - .badge(bookVm.selectedBooks.count) - .tabItem { - Image(systemName: "book.fill") - Text("Selected Books") - } - } - } -} -``` - -## 自定义样式 - -iOS 14 和 macOS 11 开始可以使用 tabViewStyle 修饰符更改 TabView 样式。比如有页面指示器的水平滚动图片。 - -显示页面指示器: - -```swift -.tabViewStyle(.page(indexDisplayMode: .always)) -``` - -`.tabViewStyle(.page(indexDisplayMode: .never))` 修饰符隐藏页面指示器。 - -水平滚动图片: - -```swift -struct ContentView: View { - let images = ["pencil", "scribble", "highlighter"] - - var body: some View { - VStack { - TabView { - ForEach(images, id: \.self) { imageName in - Image(systemName: imageName) - .resizable() - .scaledToFit() - } - } - .tabViewStyle(.page(indexDisplayMode: .always)) - .frame(height: 100) - } - } -} -``` - -分页视图 - -```swift -struct OnboardingView: View { - var body: some View { - TabView { - OnboardingPageView(imageName: "figure.mixed.cardio", - title: "Welcome", - description: "Welcome to MyApp! Get started by exploring our amazing features.") - - OnboardingPageView(imageName: "figure.archery", - title: "Discover", - description: "Discover new content and stay up-to-date with the latest news and updates.") - - OnboardingPageView(imageName: "figure.yoga", - title: "Connect", - description: "Connect with friends and share your experiences with the community.") - } - .tabViewStyle(.page(indexDisplayMode: .always)) - .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)) - } -} -``` - -`.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))` 修饰符添加了背景。这将在点周围添加一个背景,使其在任何背景下都更容易看到。 - -## 背景颜色 - -iOS 16 和 macOS 13 开始可以更改 TabView 的背景颜色。 - -```swift -struct MainScreen: View { - var body: some View { - TabView { - NavigationView { - BookListView() - .navigationTitle("图书列表") - .toolbarBackground(.yellow, for: .navigationBar) - .toolbarBackground(.visible, for: .navigationBar) - } - .tabItem { - Label("图书", systemImage: "book.closed") - } - - UserPreferencesView() - .tabItem { - Label("设置", systemImage: "gearshape") - } - .toolbarBackground(.indigo, for: .tabBar) - .toolbarBackground(.visible, for: .tabBar) - .toolbarColorScheme(.dark, for: .tabBar) - } - } -} - -struct BookListView: View { - var body: some View { - Text("这里是图书列表") - } -} - -struct UserPreferencesView: View { - var body: some View { - Text("这里是用户设置") - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/Safe Area(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/Safe Area(ap).md" deleted file mode 100644 index 69f754908..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/Safe Area(ap).md" +++ /dev/null @@ -1,105 +0,0 @@ - - -## ignoresSafeArea 忽略安全区域 - -使用 `.ignoresSafeArea()` 可以忽略安全区域。默认是所有方向都忽略。 - -如果只忽略部分方向,可以按照下面方法做: - -```swift -// 默认会同时包含 .keyboard 和 .container。 -.ignoresSafeArea(edges: .top) -.ignoresSafeArea(edges: .vertical) -.ignoresSafeArea(edges: [.leading, .trailing]) - -// 可以对安全区域分别指定 -.ignoresSafeArea(.keyboard, edges: .top) -.ignoresSafeArea(.container, edges: [.leading, .trailing]) -``` - -## safeAreaInset - -`safeAreaInset` 是 SwiftUI 中的一个属性,它允许你将视图放置在安全区域内。"安全区域"是指设备屏幕上的一块区域,这块区域不会被系统界面(如状态栏、导航栏、工具栏、Tab栏等)遮挡。 - -例如,你可以使用 `safeAreaInset` 将一个视图放置在屏幕底部的安全区域内,代码如下: - -```swift -VStack { - Text("Hello, World!") -} -.safeAreaInset(edge: .bottom, spacing: 10) { - Button("Press me") { - print("Button pressed") - } -} -``` - -在这个例子中,"Press me" 按钮会被放置在屏幕底部的安全区域内,而且距离底部有 10 个点的间距。 - -下面是更完整点的例子: - -```swift -struct ContentView: View { - @State var tasks: [TaskModel] = (0...10).map { TaskModel(name: "Task \($0)") } - @State var taskName = "" - @State var isFocused: Bool = false - - var body: some View { - NavigationView { - VStack { - List { - ForEach(tasks) { task in - Text(task.name) - } - } - .listStyle(PlainListStyle()) - .safeAreaInset(edge: .bottom) { - HStack { - TextField("Add task", text: $taskName, onCommit: { - addTask() - }) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .padding(.leading, 10) - - Button(action: { - addTask() - }) { - Image(systemName: "plus") - } - .padding(.trailing, 10) - } - .padding(.bottom, isFocused ? 0 : 10) - .background(Color.white) - } - .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in - withAnimation { - isFocused = true - } - } - .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in - withAnimation { - isFocused = false - } - } - } - .navigationBarTitle("Task List Demo") - } - } - - func addTask() { - if !taskName.isEmpty { - withAnimation { - tasks.append(TaskModel(name: taskName)) - } - taskName = "" - } - } -} - -struct TaskModel: Identifiable { - let id = UUID() - let name: String -} -``` - -用户可以在底部的输入框中输入任务名称,然后点击 "+" 按钮将任务添加到任务清单中。添加的任务会显示在屏幕的上方。当键盘出现或消失时,底部的输入框会相应地移动,以确保不会被键盘遮挡。 \ No newline at end of file diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-offset\345\201\217\347\247\273(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-offset\345\201\217\347\247\273(ap).md" deleted file mode 100644 index 6a60861cf..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-offset\345\201\217\347\247\273(ap).md" +++ /dev/null @@ -1,19 +0,0 @@ - - - -```swift -struct OffsetDemo: View { - @State var offset: CGFloat = 0 - var body: some View { - VStack { - Text("Hello, World!") - .font(.largeTitle) - .offset(y: offset) - Slider(value: $offset, in: -100...100) - .padding() - } - } -} -``` - -我们创建了一个 `Text` 视图和一个 `Slider`。`Text` 视图使用了 `.offset(y: offset)` 修饰符,这意味着它的 y 偏移量会根据 `offset` 的值改变。`Slider` 则用于改变 `offset` 的值。当你移动滑块时,`Text` 视图的位置也会相应地上下移动。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\237\272\347\241\200(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\237\272\347\241\200(ap).md" deleted file mode 100644 index 87a813f8a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\237\272\347\241\200(ap).md" +++ /dev/null @@ -1,43 +0,0 @@ - -## 基本元素样式 - -通过 `.font(.title)` 设置字体大小。 - -`.stroke(Color.blue)` 设置描边。举个例子: - -```swift -struct ContentView: View { - - var body: some View { - Rectangle() - .stroke(Color.orange, style: StrokeStyle(lineWidth: 10, lineCap: .round, dash: [30])) - .padding(30) - } -} -``` - -`StrokeStyle(lineWidth: 10, lineCap: .round, dash: [30])` 定义了描边的样式,其中 `lineWidth: 10` 表示线宽为 10,`lineCap: .round` 表示线帽样式为圆形,`dash: [30]` 表示虚线模式,数组中的数字表示虚线和间隙的交替长度。 - -## frame - -`.frame(width: 200, height:100, alignment: .topLeading)` - -- `width: 200` 表示视图的宽度为 200 点。 -- `height: 100` 表示视图的高度为 100 点。 -- `alignment: .topLeading` 表示视图的内容应该在视图的左上角对齐。`.topLeading` 是 SwiftUI 中的一个对齐方式,表示左上角对齐。 - -## Stack - -多个视图通过 Stack 视图进行对齐排列。这些 Stack 视图主要是: - - - ZStack:Z轴排列 - - VStack:垂直排列 - - HStack:横向排列 - - ## 间隔 - - 视图之间的间隔可以用 Space(),它可以在各种布局视图中使用。 - - - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\257\271\351\275\220(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\257\271\351\275\220(ap).md" deleted file mode 100644 index 5cbcdba2c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\257\271\351\275\220(ap).md" +++ /dev/null @@ -1,32 +0,0 @@ - -## frame 对齐 - -```swift -.frame(width: 100, height: 50, alignment: .topLeading) -``` - -## 可设置对齐的视图 - -在 SwiftUI 中,许多视图都接受 `alignment` 参数,用于控制其子视图的对齐方式。以下是一些常见的接受 `alignment` 参数的视图: - -- `HStack(alignment: .bottom)`:水平堆栈视图,可以控制其子视图在垂直方向上的对齐方式。 -- `VStack(alignment: .trailing)`:垂直堆栈视图,可以控制其子视图在水平方向上的对齐方式。 -- `ZStack(alignment: .center)`:深度堆栈视图,可以控制其子视图在水平和垂直方向上的对齐方式。 -- `GridRow(alignment: .firstTextBaseline)`:用于定义网格的行或列的大小,可以设置行或列中的内容的对齐方式。。 - - -## 基线对齐 - -你可以使用 `alignment` 参数来设置视图的对齐方式,包括基线对齐。以下是一个例子: - -```swift -HStack(alignment: .firstTextBaseline) { - Text("Hello") - Text("World").font(.largeTitle) -} -``` - -在这个例子中,`HStack` 是一个水平堆栈视图,它会将其子视图水平排列。`alignment: .firstTextBaseline` 是一个参数,用于设置堆栈中的内容的对齐方式。`.firstTextBaseline` 表示所有文本视图都应该根据它们的第一行的基线对齐。基线是文本字符的底部线。 - -因此,这个 `HStack` 中的两个 `Text` 视图会根据它们的第一行的基线对齐,即使它们的字体大小不同。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\261\205\344\270\255(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\261\205\344\270\255(ap).md" deleted file mode 100644 index 965866b6b..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\345\261\205\344\270\255(ap).md" +++ /dev/null @@ -1,34 +0,0 @@ - -在 SwiftUI 中,有多种方法可以使视图居中: - -## Spacer - -使用 `Spacer`:`Spacer` 是一个灵活的空间,它会尽可能地占用多的空间,从而将其周围的视图推向堆栈的两边。如果在一个视图的两边都放置一个 `Spacer`,那么这个视图就会被推到中间。 - -```swift -HStack { - Spacer() - Text("居中") - Spacer() -} -``` - -## alignment - -使用 `alignment` 参数:许多 SwiftUI 视图都接受 `alignment` 参数,用于控制其子视图的对齐方式。例如,`VStack` 和 `HStack` 都接受 `alignment` 参数。 - -```swift -VStack(alignment: .center) { - Text("居中") -} -``` - -## frame - -使用 `frame` 方法:`frame` 方法可以设置视图的尺寸和对齐方式。如果你想让一个视图在其父视图中居中,你可以使用 `frame(maxWidth: .infinity, maxHeight: .infinity)` 来使视图尽可能地占用多的空间,然后使用 `alignment: .center` 来使视图在这个空间中居中。 - -```swift -Text("居中") - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\347\225\231\347\231\275(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\347\225\231\347\231\275(ap).md" deleted file mode 100644 index 8081e38d0..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200-\347\225\231\347\231\275(ap).md" +++ /dev/null @@ -1,36 +0,0 @@ - - -## Space - -`Spacer` 是一个灵活的空间,它会尽可能地占用多的空间,从而将其周围的视图推向堆栈的两边。因此,第一个 `Text` 视图会被推到左边,第二个 `Text` 视图会被推到中间,第三个 `Text` 视图会被推到右边。 - -```swift -struct ContentView: View { - var body: some View { - HStack { - Text("左边") - Spacer() - Text("中间") - Spacer() - Text("右边") - } - } -} -``` - -下面这个例子是用 Space() 让三个视图都居右。 - -```swift -struct ContentView: View { - var body: some View { - HStack { - Spacer() - Text("视图1") - Text("视图2") - Text("视图3") - } - } -} -``` - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200\345\216\237\347\220\206(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200\345\216\237\347\220\206(ap).md" deleted file mode 100644 index 8c04c910b..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\345\237\272\347\241\200/\345\270\203\345\261\200\345\216\237\347\220\206(ap).md" +++ /dev/null @@ -1,12 +0,0 @@ - -SwiftUI 的布局系统是一个两阶段的协商过程,涉及到父视图和子视图之间的交互。 - -建议阶段:在这个阶段,父视图会向子视图提出一个建议尺寸。这个建议尺寸是父视图希望子视图的大小。例如,如果父视图是一个 VStack,那么它可能会向子视图提出一个具有明确高度、宽度未指定的建议尺寸。 - -需求阶段:在这个阶段,子视图会根据父视图的建议尺寸来确定自己的需求尺寸。子视图可以选择接受父视图的建议尺寸,也可以选择返回一个不同的尺寸。例如,一个 Text 视图可能会返回一个刚好能够容纳其文本的尺寸。 - -在这个过程中,父视图和子视图都有可能影响最终的布局结果。父视图可以通过调整建议尺寸来影响子视图的大小,而子视图可以通过返回不同的需求尺寸来影响自己的大小。 - -在一些复杂的布局场景中,可能需要进行多轮的协商才能得到最终的布局结果。例如,如果一个视图使用了 GeometryReader 来获取其在父视图中的位置和尺寸,那么 GeometryReader 可能会在布局稳定之前,多次向子视图发送新的几何信息。 - -总的来说 SwiftUI 它允许父视图和子视图之间进行协商,以达到最佳的布局效果。 \ No newline at end of file diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/AnyLayout(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/AnyLayout(ap).md" deleted file mode 100644 index f40a86c7f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/AnyLayout(ap).md" +++ /dev/null @@ -1,42 +0,0 @@ - -使用 AnyLayout 包装布局组件,可以在布局之间进行切换,同时保持动画效果。 - -```swift -struct WeatherLayout: View { - @State private var changeLayout = false - - var body: some View { - let layout = changeLayout ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout()) - - layout { - WeatherView(icon: "sun.max.fill", temperature: 25, color: .yellow) - WeatherView(icon: "cloud.rain.fill", temperature: 18, color: .blue) - WeatherView(icon: "snow", temperature: -5, color: .white) - } - .animation(.default, value: changeLayout) - .onTapGesture { - changeLayout.toggle() - } - } -} - -struct WeatherView: View { - let icon: String - let temperature: Int - let color: Color - - var body: some View { - VStack { - Image(systemName: icon) - .font(.system(size: 80)) - .foregroundColor(color) - Text("\(temperature)°") - .font(.system(size: 50)) - .foregroundColor(color) - } - .frame(width: 120, height: 120) - } -} -``` - -代码中,我们创建了一个 WeatherView 视图,它包含一个天气图标和一个温度标签。然后,我们在 WeatherLayout 视图中使用 AnyLayout 来动态改变布局。用户可以通过点击视图来在水平布局和垂直布局之间切换。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/GeometryReader(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/GeometryReader(ap).md" deleted file mode 100644 index 82356513e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/GeometryReader(ap).md" +++ /dev/null @@ -1,71 +0,0 @@ - - -在 SwiftUI 中,有多种方法可以获取和控制视图的尺寸: - -- `frame(width:60, height:60)`:这个方法会为子视图提供一个建议的尺寸,这里是 60 x 60。 -- `fixedSize()`:这个方法会为子视图提供一个未指定模式的建议尺寸,这意味着视图会尽可能地大以适应其内容。 -- `frame(minWidth: 120, maxWidth: 360)`:这个方法会将子视图的需求尺寸控制在指定的范围中,这里是宽度在 120 到 360 之间。 -- `frame(idealWidth: 120, idealHeight: 120)`:这个方法会返回一个需求尺寸,如果当前视图收到为未指定模式的建议尺寸,那么它会返回 120 x 120 的尺寸。 -- `GeometryReader`:`GeometryReader` 会将建议尺寸作为需求尺寸直接返回,这意味着它会充满全部可用区域。你可以使用 `GeometryReader` 来获取其内容的尺寸和位置。 - - -`GeometryReader` 可以获取其内容的尺寸和位置。在这个例子中,我们使用 `GeometryReader` 来获取视图的尺寸,然后打印出来。这对于理解 SwiftUI 的布局系统和调试布局问题非常有用。 - -```swift -extension View { - func logSizeInfo(_ label: String = "") -> some View { - background( - GeometryReader { proxy in - Color.clear - .onAppear(perform: { - debugPrint("\(label) Size: \(proxy.size)") - }) - } - ) - } -} - -struct ContentView: View { - var body: some View { - VStack { - Text("大标题") - .font(.largeTitle) - .logSizeInfo("大标题视图") // 打印视图尺寸 - Text("正文") - .logSizeInfo("正文视图") - } - } -} -``` - -这段代码首先定义了一个 `View` 的扩展,添加了一个 `logSizeInfo(_:)` 方法。这个方法接受一个标签字符串作为参数,然后返回一个新的视图。这个新的视图在背景中使用 `GeometryReader` 来获取并打印视图的尺寸。 - -然后,我们创建了一个 `VStack` 视图,其中包含一个 `Text` 视图。我们为 `Text` 视图调用了 `logSizeInfo(_:)` 方法,以打印其尺寸。 - - -如何利用 `GeometryReader` 来绘制一个圆形? - -```swift -struct CircleView: View { - var body: some View { - GeometryReader { proxy in - Path { path in - let radius = min(proxy.size.width, proxy.size.height) / 2 - let center = CGPoint(x: proxy.size.width / 2, y: proxy.size.height / 2) - path.addArc(center: center, radius: radius, startAngle: .zero, endAngle: .init(degrees: 360), clockwise: false) - } - .fill(Color.blue) - } - } -} -``` - -在这个例子中,我们首先获取 `GeometryReader` 的尺寸,然后计算出半径和中心点的位置。然后,我们使用 `Path` 的 `addArc(center:radius:startAngle:endAngle:clockwise:)` 方法来添加一个圆形路径。最后,我们使用 `fill(_:)` 方法来填充路径,颜色为蓝色。 - -关于 GeometryReader 性能问题 - -GeometryReader 是 SwiftUI 中的一个工具,它可以帮助我们获取视图的大小和位置。但是,它在获取这些信息时,需要等待视图被评估、布局和渲染完成。这就好比你在装修房子时,需要等待墙壁砌好、油漆干燥后,才能测量墙壁的尺寸。这个过程可能需要等待一段时间,而且可能需要多次重复,因为每次墙壁的尺寸改变,都需要重新测量。 - -这就是 GeometryReader 可能会影响性能的原因。它需要等待视图完成一轮的评估、布局和渲染,然后才能获取到尺寸数据,然后可能需要根据这些数据重新调整布局,这就需要再次进行评估、布局和渲染。这个过程可能需要重复多次,导致视图被多次重新评估和布局。 - -但是,随着 SwiftUI 的更新,这个问题已经有所改善。现在,我们可以创建自定义的布局容器,这些容器可以在布局阶段就获取到父视图的建议尺寸和所有子视图的需求尺寸,这样就可以避免反复传递尺寸数据,减少了视图的反复更新。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/Layout\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/Layout\345\215\217\350\256\256(ap).md" deleted file mode 100644 index 2bb00f8a6..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/Layout\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,42 +0,0 @@ - - - -通过实现 Layout 协议,创建一个水平堆栈布局,其中所有子视图的宽度都相等。 - -```swift -struct OptimizedEqualWidthHStack: Layout { - func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize { - if subviews.isEmpty { return .zero } - let maxSubviewSize = calculateMaxSize(subviews: subviews) - let totalSpacing = calculateSpacing(subviews: subviews).reduce(0, +) - return CGSize(width: maxSubviewSize.width * CGFloat(subviews.count) + totalSpacing, height: maxSubviewSize.height) - } - - func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) { - if subviews.isEmpty { return } - let maxSubviewSize = calculateMaxSize(subviews: subviews) - let spacings = calculateSpacing(subviews: subviews) - let placementProposal = ProposedViewSize(width: maxSubviewSize.width, height: maxSubviewSize.height) - var nextX = bounds.minX + maxSubviewSize.width / 2 - for index in subviews.indices { - subviews[index].place(at: CGPoint(x: nextX, y: bounds.midY), anchor: .center, proposal: placementProposal) - nextX += maxSubviewSize.width + spacings[index] - } - } - - private func calculateMaxSize(subviews: Subviews) -> CGSize { - return subviews.map { $0.sizeThatFits(.unspecified) }.reduce(.zero) { CGSize(width: max($0.width, $1.width), height: max($0.height, $1.height)) } - } - - private func calculateSpacing(subviews: Subviews) -> [CGFloat] { - return subviews.indices.map { $0 < subviews.count - 1 ? subviews[$0].spacing.distance(to: subviews[$0 + 1].spacing, along: .horizontal) : 0 } - } -} -``` - -上面这段代码中 sizeThatFits 方法计算并返回布局容器需要的大小,以便排列其子视图。它首先检查子视图数组是否为空,如果为空则返回 .zero。然后,它计算子视图的最大尺寸和总间距,最后返回一个 CGSize 对象,其宽度等于最大子视图宽度乘以子视图数量加上总间距,高度等于最大子视图高度。 - -placeSubviews 方法将子视图放置在布局容器中。它首先检查子视图数组是否为空,如果为空则返回。然后,它计算子视图的最大尺寸和间距,然后遍历子视图数组,将每个子视图放置在布局容器中的适当位置。 - -calculateMaxSize 和 calculateSpacing 是两个私有方法,用于计算子视图的最大尺寸和间距。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/ViewThatFits(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/ViewThatFits(ap).md" deleted file mode 100644 index affe7a827..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/ViewThatFits(ap).md" +++ /dev/null @@ -1,78 +0,0 @@ - -`ViewThatFits` 是一个自动选择最适合当前屏幕大小的子视图进行显示的视图。它会根据可用空间的大小来决定如何布局和显示子视图。 - -`ViewThatFits` 是一个在 SwiftUI 中用于选择最适合显示的视图的组件。它的工作原理如下: - -- 首先,`ViewThatFits` 会测量在特定轴(水平或垂直)或两个轴(水平和垂直)上的可用空间。这是通过 SwiftUI 的布局系统来完成的,该系统提供了当前视图的大小和位置信息。 - -- 接着,`ViewThatFits` 会测量第一个视图的大小。这是通过调用视图的 `measure(in:)` 方法来完成的,该方法返回一个包含视图理想大小的 `CGSize` 值。 - -- 如果第一个视图的大小适合可用空间,`ViewThatFits` 就会选择并放置这个视图。放置视图是通过调用视图的 `layout(in:)` 方法来完成的,该方法接受一个 `CGRect` 值,该值定义了视图在其父视图中的位置和大小。 - -- 如果第一个视图的大小不适合可用空间,`ViewThatFits` 会继续测量第二个视图的大小。如果第二个视图的大小适合可用空间,`ViewThatFits` 就会选择并放置这个视图。 - -- 如果所有视图的大小都不适合可用空间,`ViewThatFits` 会选择并放置 `ViewBuilder` 闭包中的最后一个视图。`ViewBuilder` 是一个特殊的闭包,它可以根据其内容动态创建视图。 - -```swift -ViewThatFits(in: .horizontal) { - Text("晴天,气温25°") // 宽度在200到300之间 - .font(.title) - .foregroundColor(.yellow) - Text("晴天,25°") // 宽度在150到200之间 - .font(.title) - .foregroundColor(.gray) - Text("晴25") // 宽度在100到150之间 - .font(.title) - .foregroundColor(.white) -} -.border(Color.green) // ViewThatFits所需的大小 -.frame(width:200) -.border(Color.orange) // 父视图提议的大小 -``` - -在不同的宽度下,ViewThatFits 会选择不同的视图进行显示。在上面的示例中,当父视图的宽度在100到150之间时,ViewThatFits 会选择显示 "晴25" 这个视图。 - -通过 ViewThatFits 来确定内容是否可滚动。 - -```swift -struct ContentView: View { - @State var step: CGFloat = 3 - var count: Int { - Int(step) - } - - var body: some View { - VStack(alignment:.leading) { - Text("数量: \(count)") - .font(.title) - .foregroundColor(.blue) - Stepper("数量", value: $step, in: 3...20) - - ViewThatFits { - content - ScrollView(.horizontal,showsIndicators: true) { - content - } - } - } - .padding() - } - - var content: some View { - HStack { - ForEach(0 ..< count, id: \.self) { i in - Rectangle() - .fill(Color.green) - .frame(width: 30, height: 30) - .overlay( - Text("\(i)") - .font(.headline) - .foregroundColor(.white) - ) - } - } - } -} -``` - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/alignmentGuide(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/alignmentGuide(ap).md" deleted file mode 100644 index 1b8a1ba9b..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/alignmentGuide(ap).md" +++ /dev/null @@ -1,34 +0,0 @@ - - -`alignmentGuide`是SwiftUI中的一个修饰符,它允许你自定义视图的对齐方式。你可以使用它来调整视图在其父视图或同级视图中的位置。 - -当你在一个视图上应用`alignmentGuide`修饰符时,你需要提供一个对齐标识符和一个闭包。对齐标识符定义了你想要调整的对齐方式(例如,`.leading`,`.trailing`,`.center`等)。闭包接收一个参数,这个参数包含了视图的尺寸,你可以使用这个参数来计算对齐指南的偏移量。 - -举个例子: - -```swift -struct ContentView: View { - var body: some View { - HStack(alignment: .top) { - CircleView() - .alignmentGuide(.top) { vd in - vd[.top] + 50 - } - CircleView() - } - .padding() - .border(Color.gray) - } - - struct CircleView: View { - var body: some View { - Circle() - .fill(Color.mint) - .frame(width: 50, height: 50) - } - } -} -``` - -在HStack中,第一个CircleView使用了.alignmentGuide修饰符,这使得它在顶部对齐时向下偏移了50个单位。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/\345\270\203\345\261\200\350\277\233\351\230\266-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/\345\270\203\345\261\200\350\277\233\351\230\266-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" deleted file mode 100644 index 1db8bd509..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\345\270\203\345\261\200\347\273\204\344\273\266/\345\270\203\345\261\200\350\277\233\351\230\266/\345\270\203\345\261\200\350\277\233\351\230\266-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" +++ /dev/null @@ -1,18 +0,0 @@ - -## WWDC - -23 -- [Go beyond the window with SwiftUI - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10111) - -22 -- [Bring multiple windows to your SwiftUI app - WWDC22 - Videos - Apple Developer](https://developer.apple.com/wwdc22/10061) 为您的 SwiftUI App 添加多个窗口 - -20 -- [Stacks, Grids, and Outlines in SwiftUI - WWDC20 - Videos - Apple Developer](https://developer.apple.com/wwdc20/10031) SwiftUI 中的叠放、网格和大纲 -- [How to make your app look great on every screen - Discover - Apple Developer](https://developer.apple.com/news/?id=nixcb564) How to make your app look greate on every screen - -## 官方接口文档 -- [Layout fundamentals | 接口](https://developer.apple.com/documentation/swiftui/layout-fundamentals) -- [Layout adjustments | 接口](https://developer.apple.com/documentation/swiftui/layout-adjustments) -- [Custom layout | 接口](https://developer.apple.com/documentation/swiftui/custom-layout) -- [View groupings | 接口](https://developer.apple.com/documentation/swiftui/view-groupings) diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/ForEach(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/ForEach(ap).md" deleted file mode 100644 index 74cad2de7..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/ForEach(ap).md" +++ /dev/null @@ -1,136 +0,0 @@ - -## 使用 - -在 SwiftUI 中,`ForEach` 是一个结构体,它可以创建一组视图,每个视图都有一个与数据集中的元素相对应的唯一标识符。这对于在列表或其他集合视图中显示数据非常有用。 - -以下视图集会用到 ForEach: - -- List -- ScrollView -- LazyVStack / LazyHStack -- Picker -- Grids (LazyVGrid / LazyHGrid) - -例如,如果你有一个 `BookmarkModel` 的数组,并且你想为每个书签创建一个文本视图,你可以这样做: - -```swift -struct ContentView: View { - var bookmarks: [BookmarkModel] - - var body: some View { - List { - ForEach(bookmarks) { bookmark in - Text(bookmark.name) - } - } - } -} -``` - -`ForEach` 遍历 `bookmarks` 数组,并为每个 `BookmarkModel` 对象创建一个 `Text` 视图。`bookmark` 参数是当前遍历的 `BookmarkModel` 对象。 - -`BookmarkModel` 必须遵循 `Identifiable` 协议,这样 SwiftUI 才能知道如何唯一地标识每个视图。在你的代码中,`BookmarkModel` 已经有一个 `id` 属性,所以你只需要让 `BookmarkModel` 遵循 `Identifiable` 协议即可: - -```swift -final class BookmarkModel: Identifiable { - // your code here -} -``` - -## 使用索引范围进行编号 - -你可以使用 `ForEach` 结构体的另一个版本,它接受一个范围作为其数据源。这个范围可以是一个索引范围,这样你就可以为每个项目编号。 - -例如,如果你有一个 `BookmarkModel` 的数组,并且你想为每个书签创建一个文本视图,并在前面添加一个编号,你可以这样做: - -```swift -struct ContentView: View { - var bookmarks: [BookmarkModel] - - var body: some View { - List { - ForEach(bookmarks.indices, id: \.self) { index in - Text("\(index + 1). \(bookmarks[index].name)") - } - } - } -} -``` - -在这个例子中,`ForEach` 遍历 `bookmarks` 数组的索引,并为每个 `BookmarkModel` 对象创建一个 `Text` 视图。`index` 参数是当前遍历的索引。我们使用 `\(index + 1). \(bookmarks[index].name)` 来创建一个带有编号的文本视图。请注意,我们使用 `index + 1` 而不是 `index`,因为数组的索引是从 0 开始的,但我们通常希望编号是从 1 开始的。 - -## 使用 enumerated 编号 - - `enumerated()`  - -以下是一个例子: - -```swift -struct ContentView: View { - var bookmarks: [BookmarkModel] - - var body: some View { - List { - ForEach(Array(bookmarks.enumerated()), id: \.element.id) { index, bookmark in - Text("\(index). \(bookmark.name)") - } - } - } -} -``` - -我们使用 `Array(bookmarks.enumerated())` 来创建一个元组数组,每个元组包含一个索引和一个 `BookmarkModel` 对象。然后,我们使用 `ForEach` 遍历这个元组数组,并为每个元组创建一个 `Text` 视图。`index` 参数是当前遍历的索引,`bookmark` 参数是当前遍历的 `BookmarkModel` 对象。 - -## 使用 zip 编号 - -`zip(_:_:)` 函数可以将两个序列合并为一个元组序列。你可以使用这个函数和 `ForEach` 结构体来为数组中的每个元素添加一个编号。 - -例如,如果你有一个 `BookmarkModel` 的数组,并且你想为每个书签创建一个文本视图,并在前面添加一个编号,你可以这样做: - -```swift -struct ContentView: View { - var bookmarks: [BookmarkModel] - - var body: some View { - List { - ForEach(Array(zip(1..., bookmarks)), id: \.1.id) { index, bookmark in - Text("\(index). \(bookmark.name)") - } - } - } -} -``` - -写出扩展,方便调用 - -```swift -@dynamicMemberLookup -struct Numbered { - var number: Int - var element: Element - - subscript(dynamicMember keyPath: WritableKeyPath) -> T { - get { element[keyPath: keyPath] } - set { element[keyPath: keyPath] = newValue } - } -} - -extension Sequence { - func numbered(startingAt start: Int = 1) -> [Numbered] { - zip(start..., self) - .map { Numbered(number: $0.0, element: $0.1) } - } -} - -extension Numbered: Identifiable where Element: Identifiable { - var id: Element.ID { element.id } -} -``` - -使用: - -```swift -ForEach(bookmark.numbered()) { numberedBookmark in - Text("\(numberedBookmark.number). \(numberedBookmark.name)") -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Grid(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Grid(ap).md" deleted file mode 100644 index f8272f3f4..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Grid(ap).md" +++ /dev/null @@ -1,71 +0,0 @@ - -Grid 会将最大的一个单元格大小应用于所有单元格 - -代码例子: - -```swift -struct ContentView: View { - var body: some View { - Grid(alignment: .center, - horizontalSpacing: 30, - verticalSpacing: 8) { - GridRow { - Text("Tropical") - Text("Mango") - Text("Pineapple") - .gridCellColumns(2) - } - GridRow(alignment: .bottom) { - Text("Leafy") - Text("Spinach") - Text("Kale") - Text("Lettuce") - } - } - } -} -``` - -`gridCellAnchor` 可以让 GridRow 给自己设置对齐方式。 - -`gridCellColumns()` modifier 可以让一个单元格跨多列。 - -GridRow 的间距通过 Grid 的 `horizontalSpacing` 和 `verticalSpacing` 参数来控制。 - -```swift -struct ContentView: View { - let numbers: [[Int]] = [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9] - ] - - var body: some View { - Grid(horizontalSpacing: 0, verticalSpacing: 0) { - ForEach(numbers.indices, id: \.self) { i in - GridRow { - ForEach(numbers[i].indices, id: \.self) { j in - Text("\(numbers[i][j])") - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.gray.opacity(0.2)) - .border(Color.gray, width: 0.5) - } - } - } - } - } -} -``` - -按照以上代码这样写,每个数字 GridRow 之间的间隔就是0了。 - -空白的单元格可以这样写: - -```swift -Color.clear - .gridCellUnsizedAxes([.horizontal, .vertical]) -``` - - - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Lazy\345\256\271\345\231\250/LazyVGrid\345\222\214LazyHGrid(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Lazy\345\256\271\345\231\250/LazyVGrid\345\222\214LazyHGrid(ap).md" deleted file mode 100644 index cbf03cc81..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Lazy\345\256\271\345\231\250/LazyVGrid\345\222\214LazyHGrid(ap).md" +++ /dev/null @@ -1,69 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/lazyvgridandlazyhgrid-ap01.jpeg) - -列的设置有三种,这三种也可以组合用。 - -* GridItem(.fixed(10)) 会固定设置有多少列。 -* GridItem(.flexible()) 会充满没有使用的空间。 -* GridItem(.adaptive(minimum: 10)) 表示会根据设置大小自动设置有多少列展示。 - -示例: - -```swift -struct PlayLazyVGridAndLazyHGridView: View { - @State private var colors: [String:Color] = [ - "red" : .red, - "orange" : .orange, - "yellow" : .yellow, - "green" : .green, - "mint" : .mint, - "teal" : .teal, - "cyan" : .cyan, - "blue" : .blue, - "indigo" : .indigo, - "purple" : .purple, - "pink" : .pink, - "brown" : .brown, - "gray" : .gray, - "black" : .black - ] - - var body: some View { - ScrollView { - LazyVGrid(columns: [ - GridItem(.adaptive(minimum: 50), spacing: 10) - ], pinnedViews: [.sectionHeaders]) { - Section(header: - Text("🎨调色板") - .font(.title) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(RoundedRectangle(cornerRadius: 0) - .fill(.black.opacity(0.1))) - ) { - ForEach(Array(colors.keys), id: \.self) { k in - colors[k].frame(height:Double(Int.random(in: 50...150))) - .overlay( - Text(k) - ) - .shadow(color: .black, radius: 2, x: 0, y: 2) - } - } - } - .padding() - - LazyVGrid(columns: [ - GridItem(.adaptive(minimum: 20), spacing: 10) - ]) { - Section(header: Text("图标集").font(.title)) { - ForEach(1...30, id: \.self) { i in - Image("p\(i)") - .resizable() - .aspectRatio(contentMode: .fit) - .shadow(color: .black, radius: 2, x: 0, y: 2) - } - } - } - .padding() - } - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Lazy\345\256\271\345\231\250/LazyVStack\345\222\214LazyHStack(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Lazy\345\256\271\345\231\250/LazyVStack\345\222\214LazyHStack(ap).md" deleted file mode 100644 index 181a00d04..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Lazy\345\256\271\345\231\250/LazyVStack\345\222\214LazyHStack(ap).md" +++ /dev/null @@ -1,26 +0,0 @@ -LazyVStack 和 LazyHStack 里的视图只有在滚到时才会被创建。 - -```swift -struct PlayLazyVStackAndLazyHStackView: View { - var body: some View { - ScrollView { - LazyVStack { - ForEach(1...300, id: \.self) { i in - PLHSRowView(i: i) - } - } - } - } -} - -struct PLHSRowView: View { - let i: Int - var body: some View { - Text("第 \(i) 个") - } - init(i: Int) { - print("第 \(i) 个初始化了") // 用来查看什么时候创建的。 - self.i = i - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List(ap).md" deleted file mode 100644 index 671918fc2..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List(ap).md" +++ /dev/null @@ -1,265 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/list-ap01.jpeg) - -List 除了能够展示数据外,还有下拉刷新、过滤搜索和侧滑 Swipe 动作提供更多 Cell 操作的能力。 - -通过 List 的可选子项参数提供数据模型的关键路径来制定子项路劲,还可以实现大纲视图,使用 DisclosureGroup 和 OutlineGroup 可以进一步定制大纲视图。 - -使用 `.listRowSeparator(.hidden, edges: .all)` 可以隐藏分割线。 - -下面是 List 使用,包括了 DisclosureGroup 和 OutlineGroup 的演示代码: - -```swift -struct PlayListView: View { - @StateObject var l: PLVM = PLVM() - @State private var s: String = "" - - var outlineModel = [ - POutlineModel(title: "文件夹一", iconName: "folder.fill", children: [ - POutlineModel(title: "个人", iconName: "person.crop.circle.fill"), - POutlineModel(title: "群组", iconName: "person.2.circle.fill"), - POutlineModel(title: "加好友", iconName: "person.badge.plus") - ]), - POutlineModel(title: "文件夹二", iconName: "folder.fill", children: [ - POutlineModel(title: "晴天", iconName: "sun.max.fill"), - POutlineModel(title: "夜间", iconName: "moon.fill"), - POutlineModel(title: "雨天", iconName: "cloud.rain.fill", children: [ - POutlineModel(title: "雷加雨", iconName: "cloud.bolt.rain.fill"), - POutlineModel(title: "太阳雨", iconName: "cloud.sun.rain.fill") - ]) - ]), - POutlineModel(title: "文件夹三", iconName: "folder.fill", children: [ - POutlineModel(title: "电话", iconName: "phone"), - POutlineModel(title: "拍照", iconName: "camera.circle.fill"), - POutlineModel(title: "提醒", iconName: "bell") - ]) - ] - - var body: some View { - HStack { - // List 通过$语法可以将集合的元素转换成可绑定的值 - List { - ForEach($l.ls) { $d in - PRowView(s: d.s, i: d.i) - .listRowInsets(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15)) - .listRowBackground(Color.black.opacity(0.2)) - } - } - .refreshable { - // 下拉刷新 - } - .searchable(text: $s) // 搜索 - .onChange(of: s) { newValue in - print("搜索关键字:\(s)") - } - - Divider() - - // 自定义 List - VStack { - PCustomListView($l.ls) { $d in - PRowView(s: d.s, i: d.i) - } - // 添加数据 - Button { - l.ls.append(PLModel(s: "More", i: 0)) - } label: { - Text("添加") - } - } - .padding() - - Divider() - - // 使用大纲 - List(outlineModel, children: \.children) { i in - Label(i.title, systemImage: i.iconName) - } - - Divider() - - // 自定义大纲视图 - VStack { - Text("可点击标题展开") - .font(.headline) - PCOutlineListView(d: outlineModel, c: \.children) { i in - Label(i.title, systemImage: i.iconName) - } - } - .padding() - - Divider() - - // 使用 OutlineGroup 实现大纲视图 - VStack { - Text("OutlineGroup 实现大纲") - - OutlineGroup(outlineModel, children: \.children) { i in - Label(i.title, systemImage: i.iconName) - } - - // OutlineGroup 和 List 结合 - Text("OutlineGroup 和 List 结合") - List { - ForEach(outlineModel) { s in - Section { - OutlineGroup(s.children ?? [], children: \.children) { i in - Label(i.title, systemImage: i.iconName) - } - } header: { - Label(s.title, systemImage: s.iconName) - } - - } // end ForEach - } // end List - } // end VStack - } // end HStack - } // end body -} - -// MARK: - 自定义大纲视图 -struct PCOutlineListView: View where D: RandomAccessCollection, D.Element: Identifiable, Content: View { - private let v: PCOutlineView - - init(d: D, c: KeyPath, content: @escaping (D.Element) -> Content) { - self.v = PCOutlineView(d: d, c: c, content: content) - } - - var body: some View { - List { - v - } - } -} - -struct PCOutlineView: View where D: RandomAccessCollection, D.Element: Identifiable, Content: View { - let d: D - let c: KeyPath - let content: (D.Element) -> Content - @State var isExpanded = true // 控制初始是否展开的状态 - - var body: some View { - ForEach(d) { i in - if let sub = i[keyPath: c] { - PCDisclosureGroup(content: PCOutlineView(d: sub, c: c, content: content), label: content(i)) - } else { - content(i) - } // end if - } // end ForEach - } // end body -} - -struct PCDisclosureGroup: View where C: View, L: View { - @State var isExpanded = false - var content: C - var label: L - var body: some View { - DisclosureGroup(isExpanded: $isExpanded) { - content - } label: { - Button { - isExpanded.toggle() - } label: { - label - } - .buttonStyle(.plain) - } - } -} - -// MARK: - 大纲模式数据模型 -struct POutlineModel: Hashable, Identifiable { - var id = UUID() - var title: String - var iconName: String - var children: [POutlineModel]? -} - -// MARK: - List 的抽象,数据兼容任何集合类型 -struct PCustomListView: View where D.Element: Identifiable { - @Binding var data: D - var content: (Binding) -> Content - - init(_ data: Binding, content: @escaping (Binding) -> Content) { - self._data = data - self.content = content - } - - var body: some View { - List { - Section { - ForEach($data, content: content) - .onMove { indexSet, offset in - data.move(fromOffsets: indexSet, toOffset: offset) - } - .onDelete { indexSet in - data.remove(atOffsets: indexSet) // macOS 暂不支持 - } - } header: { - Text("第一栏,共 \(data.count) 项") - } footer: { - Text("The End") - } - } - .listStyle(.plain) // 有.automatic、.inset、.plain、sidebar,macOS 暂不支持的有.grouped 和 .insetGrouped - } -} - -// MARK: - Cell 视图 -struct PRowView: View { - var s: String - var i: Int - var body: some View { - HStack { - Text("\(i):") - Text(s) - } - } -} - -// MARK: - 数据模型设计 -struct PLModel: Hashable, Identifiable { - let id = UUID() - var s: String - var i: Int -} - -final class PLVM: ObservableObject { - @Published var ls: [PLModel] - init() { - ls = [PLModel]() - for i in 0...20 { - ls.append(PLModel(s: "\(i)", i: i)) - } - } -} -``` - -list 支持 Section footer。 - -list 分隔符可以自定义,使用 `HorizontalEdge.leading` 和 `HorizontalEdge.trailing` 。 - -list 不使用 UITableView 了。 - -今年 list 还新增了一个 [EditOperation](https://developer.apple.com/documentation/swiftui/editoperations) 可以自动生成移动和删除,新增了 edits 参数,传入 `[.delete, .move]` 数组即可。这也是一个演示如何更好扩展和配置功能的方式。 - -`.searchable` 支持 token 和 scope,示例如下: -```swift -struct PSearchTokensAndScopes: View { - enum AttendanceScope { - case inPerson, online - } - @State private var queryText: String - @State private var queryTokens: [InvitationToken] - @State private var scope: AttendanceScope - - var body: some View { - invitationCountView() - .searchable(text: $queryText, tokens: $queryTokens, scope: $scope) { token in - Label(token.diplayName, systemImage: token.systemImage) - } scopes: { - Text("In Person").tag(AttendanceScope.inPerson) - Text("Online").tag(AttendanceScope.online) - } - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\344\270\213\346\213\211\345\210\267\346\226\260(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\344\270\213\346\213\211\345\210\267\346\226\260(ap).md" deleted file mode 100644 index 8fa17f47c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\344\270\213\346\213\211\345\210\267\346\226\260(ap).md" +++ /dev/null @@ -1,28 +0,0 @@ - -你可以使用 `.refreshable()` 修饰符来添加下拉刷新功能。以下是一个例子: - -```swift -struct ContentView: View { - @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - - var body: some View { - List { - ForEach(items, id: \.self) { item in - Text(item) - } - } - .refreshable { - await refresh() - } - } - - func refresh() async { - // 这里是你的刷新逻辑 - // 例如,你可以从网络获取新的数据,然后更新 items 数组 - // 这里我们只是简单地将 items 数组反转 - items.reverse() - } -} -``` - -在这个例子中,我们创建了一个包含五个元素的 List,并添加了下拉刷新功能。当用户下拉 List 时,`refresh()` 方法会被调用,然后我们将 items 数组反转,从而模拟刷新操作。注意,`refresh()` 方法需要是一个异步方法,因为刷新操作通常需要一些时间来完成。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\345\212\240\350\275\275\346\233\264\345\244\232(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\345\212\240\350\275\275\346\233\264\345\244\232(ap).md" deleted file mode 100644 index 7e5e9e8ff..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\345\212\240\350\275\275\346\233\264\345\244\232(ap).md" +++ /dev/null @@ -1,34 +0,0 @@ - - -你可以通过检测列表滚动到底部来实现加载更多的功能。以下是一个简单的例子: - -```swift -struct ContentView: View { - @State private var items = Array(0..<20) - - var body: some View { - List { - ForEach(items, id: \.self) { item in - Text("Item \(item)") - .onAppear { - if item == items.last { - loadMore() - } - } - } - } - .onAppear(perform: loadMore) - } - - func loadMore() { - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - let newItems = Array(self.items.count..: View where D: RandomAccessCollection, D.Element: Identifiable, Content: View { - private let v: SPOutlineView - - init(d: D, c: KeyPath, content: @escaping (D.Element) -> Content) { - self.v = SPOutlineView(d: d, c: c, content: content) - } - - var body: some View { - List { - v - } - } -} - -struct SPOutlineView: View where D: RandomAccessCollection, D.Element: Identifiable, Content: View { - let d: D - let c: KeyPath - let content: (D.Element) -> Content - @State var isExpanded = true // 控制初始是否展开的状态 - - var body: some View { - ForEach(d) { i in - if let sub = i[keyPath: c] { - SPDisclosureGroup(content: SPOutlineView(d: sub, c: c, content: content), label: content(i)) - } else { - content(i) - } // end if - } // end ForEach - } // end body -} - -struct SPDisclosureGroup: View where C: View, L: View { - @State var isExpanded = false - var content: C - var label: L - var body: some View { - DisclosureGroup(isExpanded: $isExpanded) { - content - } label: { - Button { - withAnimation { - isExpanded.toggle() - } - } label: { - label - } - .buttonStyle(.plain) - } - - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\345\256\214\345\205\250\345\217\257\347\202\271\345\207\273\347\232\204\350\241\214(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\345\256\214\345\205\250\345\217\257\347\202\271\345\207\273\347\232\204\350\241\214(ap).md" deleted file mode 100644 index 76e2dd055..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\345\256\214\345\205\250\345\217\257\347\202\271\345\207\273\347\232\204\350\241\214(ap).md" +++ /dev/null @@ -1,21 +0,0 @@ - -使用 `.contentShape(Rectangle())` 可以使整个区域都可点击 - -```swift -struct ContentView: View { - var body: some View { - List { - ForEach(1..<50) { num in - HStack { - Text("\(num)") - Spacer() - } - .contentShape(Rectangle()) - .onTapGesture { - print("Clicked \(num)") - } - } - } // end list - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\346\220\234\347\264\242(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\346\220\234\347\264\242(ap).md" deleted file mode 100644 index d08cd79bf..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\346\220\234\347\264\242(ap).md" +++ /dev/null @@ -1,184 +0,0 @@ - -## 搜索和搜索建议 - -你可以使用 `.searchable()` 修饰符的 `suggestions` 参数来提供搜索建议。以下是一个例子: - -```swift -struct ContentView: View { - @State private var searchText = "" - @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - - var body: some View { - NavigationView { - List { - ForEach(items.filter({ searchText.isEmpty ? true : $0.contains(searchText) }), id: \.self) { item in - Text(item) - } - } - .searchable(text: $searchText, suggestions: { - Button(action: { - searchText = "Item 1" - }) { - Text("Item 1") - } - Button(action: { - searchText = "Item 2" - }) { - Text("Item 2") - } - }) - .navigationBarTitle("Items") - } - } -} -``` - -在这个例子中,我们创建了一个包含五个元素的 List,并添加了一个搜索框。当用户在搜索框中输入文本时,List 会自动更新以显示匹配的元素。同时,我们提供了两个搜索建议 "Item 1" 和 "Item 2",用户可以点击这些建议来快速填充搜索框。 - - -## 在列表中显示搜索建议 - -```swift -struct ContentView: View { - @Environment(\.searchSuggestionsPlacement) var placement - @State private var searchText = "" - @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - - var body: some View { - NavigationView { - List { - SearchSuggestionView() - ForEach(items.filter({ searchText.isEmpty ? true : $0.contains(searchText) }), id: \.self) { item in - Text(item) - } - } - .searchable(text: $searchText, suggestions: { - VStack { - Button(action: { - searchText = "Item 1" - }) { - Text("Item 1") - } - Button(action: { - searchText = "Item 2" - }) { - Text("Item 2") - } - } - .searchSuggestions(.hidden, for: .content) - }) - .navigationBarTitle("Items") - } - } - - @ViewBuilder - func SearchSuggestionView() -> some View { - if placement == .content { - Button(action: { - searchText = "Item 1" - }) { - Text("Item 1") - } - Button(action: { - searchText = "Item 2" - }) { - Text("Item 2") - } - } - } -} -``` - -## 搜索状态 - -搜索中 - -```swift -@Environment(\.isSearching) var isSearching -``` - -关闭搜索 - -```swift -@Environment(\.dismissSearch) var dismissSearch -``` - -提交搜索 - -```swift -List { - ... -} -.searchable(text: $vm.searchTerm) -.onSubmit(of: .search) { - //... -} -``` - -## 搜索栏外观 - -占位文字说明 - -```swift -.searchable(text: $wwdcVM.searchText, prompt: "搜索 WWDC Session 内容") -``` - -一直显示搜索栏 - -```swift -.searchable(text: $wwdcVM.searchText, - placement: .navigationBarDrawer(displayMode:.always)) -``` - -更改搜索栏的位置 - -```swift -.searchable(text: $wwdcVM.searchText, placement: .sidebar) -``` - -## 搜索去抖动 - -你可以使用 Combine 框架来实现搜索的去抖动功能。以下是一个例子: - -```swift -import SwiftUI -import Combine - -class SearchViewModel: ObservableObject { - @Published var searchText = "" - @Published var searchResults: [String] = [] - - private var cancellables = Set() - - init() { - $searchText - .debounce(for: .milliseconds(500), scheduler: RunLoop.main) - .sink { [weak self] in self?.search($0) } - .store(in: &cancellables) - } - - private func search(_ text: String) { - // 这里是你的搜索逻辑 - // 例如,你可以从一个数组中过滤出匹配的元素 - let items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - searchResults = items.filter { $0.contains(text) } - } -} - -struct ContentView: View { - @StateObject private var viewModel = SearchViewModel() - - var body: some View { - VStack { - TextField("Search", text: $viewModel.searchText) - .padding() - List(viewModel.searchResults, id: \.self) { result in - Text(result) - } - } - } -} -``` - -在这个例子中,我们创建了一个 `SearchViewModel` 类,它有一个 `searchText` 属性和一个 `searchResults` 属性。当 `searchText` 属性的值发生变化时,我们使用 Combine 的 `debounce(for:scheduler:)` 方法来延迟执行搜索操作,从而实现去抖动功能。然后我们在 `ContentView` 中使用这个 `SearchViewModel` 来显示搜索框和搜索结果。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\347\247\273\345\212\250\345\205\203\347\264\240(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\347\247\273\345\212\250\345\205\203\347\264\240(ap).md" deleted file mode 100644 index 9790e6630..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\347\247\273\345\212\250\345\205\203\347\264\240(ap).md" +++ /dev/null @@ -1,29 +0,0 @@ - -你可以使用 `.onMove(perform:)` 修饰符来允许用户移动 List 中的元素。以下是一个例子: - -```swift -struct ContentView: View { - @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - - var body: some View { - NavigationView { - List { - ForEach(items, id: \.self) { item in - Text(item) - } - .onMove(perform: move) - } - .toolbar { - EditButton() - } - } - } - - private func move(from source: IndexSet, to destination: Int) { - items.move(fromOffsets: source, toOffset: destination) - } -} -``` - -在这个例子中,我们创建了一个包含五个元素的 List。我们使用 `.onMove(perform:)` 修饰符来允许用户移动这些元素,并提供了一个 `move(from:to:)` 方法来处理移动操作。我们还添加了一个 `EditButton`,用户可以点击它来进入编辑模式,然后就可以移动元素了。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\347\264\242\345\274\225\346\240\207\351\242\230(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\347\264\242\345\274\225\346\240\207\351\242\230(ap).md" deleted file mode 100644 index 864291a65..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\347\264\242\345\274\225\346\240\207\351\242\230(ap).md" +++ /dev/null @@ -1,72 +0,0 @@ - -这个代码是在创建一个带有索引标题的列表,用户可以通过拖动索引标题来快速滚动列表。 - -```swift -import SwiftUI - -... - -struct ContentView: View { - ... - var body: some View { - ScrollViewReader { proxy in - List { - ArticleListView - } - .listStyle(InsetGroupedListStyle()) - .overlay(IndexView(proxy: proxy)) - } - } - ... -} - -struct IndexView: View { - let proxy: ScrollViewProxy - let titles: [String] - @GestureState private var dragLocation: CGPoint = .zero - - var body: some View { - VStack { - ForEach(titles, id: \.self) { title in - TitleView() - .background(drag(title: title)) - } - } - .gesture( - DragGesture(minimumDistance: 0, coordinateSpace: .global) - .updating($dragLocation) { value, state, _ in - state = value.location - } - ) - } - - func drag(title: String) -> some View { - GeometryReader { geometry in - drag(geometry: geometry, title: title) - } - } - - func drag(geometry: GeometryProxy, title: String) -> some View { - if geometry.frame(in: .global).contains(dragLocation) { - DispatchQueue.main.async { - proxy.scrollTo(title, anchor: .center) - } - } - return Rectangle().fill(Color.clear) - } - ... -} -... -``` - -上面代码中 `ContentView` 是主视图,它包含一个 `List` 和一个 `IndexView`。`List` 中的内容由 `ArticleListView` 提供。`IndexView` 是一个自定义视图,它显示了所有的索引标题。 - -`IndexView` 接受一个 `ScrollViewProxy` 和一个标题数组。它使用 `VStack` 和 `ForEach` 来创建一个垂直的索引标题列表。每个标题都是一个 `TitleView`,并且它有一个背景,这个背景是通过 `drag(title:)` 方法创建的。 - -`drag(title:)` 方法接受一个标题,并返回一个视图。这个视图是一个 `GeometryReader`,它可以获取其包含的视图的几何信息。然后,这个 `GeometryReader` 使用 `drag(geometry:title:)` 方法来创建一个新的视图。 - -`drag(geometry:title:)` 方法接受一个 `GeometryProxy` 和一个标题,并返回一个视图。如果 `GeometryProxy` 的全局帧包含当前的拖动位置,那么这个方法将返回一个特定的视图。 - -`IndexView` 还有一个手势,这个手势是一个 `DragGesture`。当用户拖动索引标题时,这个手势会更新 `dragLocation` 属性的值,这个属性是一个 `@GestureState` 属性,它表示当前的拖动位置。 - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\350\256\276\347\275\256\346\240\267\345\274\217(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\350\256\276\347\275\256\346\240\267\345\274\217(ap).md" deleted file mode 100644 index f600c81a4..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\350\256\276\347\275\256\346\240\267\345\274\217(ap).md" +++ /dev/null @@ -1,132 +0,0 @@ - - -## 内置样式 - -通过 `.listStyle` 修饰符可以用系统内置样式更改 List 外观。 - -```swift -List { - ... -} -.listStyle(.sidebar) -``` - -不同平台有不同的选项 - - -| ListStyle | iOS | macOS | watchOS | tvOS | -| ------------ | ------- | ------------ | ---------- | -------- | -| plain | iOS 13+ | macOS 10.15+ | watchOS 6+ | tvOS 13+ | -| sidebar | iOS 14+ | macOS 10.15+ | - | - | -| inset | iOS 13+ | macOS 11.15+ | - | - | -| grouped | iOS 13+ | - | - | tvOS 13+ | -| insetGrouped | iOS 14+ | - | - | - | -| bordered | - | macOS 12+ | - | - | -| carousel | - | - | watchOS 6+ | - | -| elliptical | - | - | watchOS 7+ | - | - - -## 行高 - -```swift -List { - ... -} -.environment(\.defaultMinListRowHeight, 100) -.environment(\.defaultMinListHeaderHeight, 50) -``` - -## 分隔符 - -listSectionSeparator 和 listRowSeparator 隐藏行和 Section 分隔符。 - -listRowSeparatorTint 和 listSectionSeparatorTint 更改分隔符颜色 - -例如: - -```swift -.listRowSeparatorTint(.cyan, edges: .bottom) -``` - -## 背景 - -`.alternatingRowBackgrounds()` 可以让 List 的行底色有区分。 - -listRowBackground 调整行的背景颜色 - -更改背景颜色前需要隐藏内容背景 - -```swift -List { - ... -} -.scrollContentBackground(.hidden) -.background(Color.cyan) -``` - -这个方法同样可用于 ScrollView 和 TextEditor。 - -你可以使用 `.listRowBackground()` 修饰符来更改列表行的背景。以下是一个例子: - -```swift -struct ContentView: View { - var body: some View { - List { - ForEach(0..<5) { index in - Text("Row \(index)") - .listRowBackground(index % 2 == 0 ? Color.blue : Color.green) - } - } - } -} -``` - -在这个例子中,我们创建了一个包含五个元素的 List。我们使用 `.listRowBackground()` 修饰符来更改每个元素的背景颜色。如果元素的索引是偶数,我们将背景颜色设置为蓝色,否则我们将背景颜色设置为绿色。 - -## Section - -你可以使用 `Section` 视图的 `header` 和 `footer` 参数来添加头部和尾部。以下是一个例子: - -```swift -struct ContentView: View { - var body: some View { - List { - Section { - ForEach(0..<5) { index in - Text("Row \(index)") - } - } header: { - Text("Header").font(.title) - } footer: { - Text("Footer").font(.caption) - } - } - } -} -``` - -headerProminence(.increase) 可以增加 Section Header 的大小。 - -## safeAreaInset - -你可以使用 `.safeAreaInset()` 修饰符来调整视图的安全区域插入。以下是一个例子: - -```swift -struct ContentView: View { - var body: some View { - List { - ForEach(0..<5) { index in - Text("Row \(index)") - } - } - .safeAreaInset(edge: .top, spacing: 20) { - Text("Header") - .frame(maxWidth: .infinity, alignment: .center) - .background(Color.blue) - .foregroundColor(.white) - } - } -} -``` - -在这个例子中,我们创建了一个包含五个元素的 List。然后我们使用 `.safeAreaInset()` 修饰符来在 List 的顶部添加一个 Header。我们将 `edge` 参数设置为 `.top`,将 `spacing` 参数设置为 20,然后提供一个视图作为 Header。这个 Header 是一个文本视图,它的背景颜色是蓝色,前景颜色是白色,它被居中对齐,并且它的宽度和 List 的宽度相同。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\350\275\273\346\211\253\346\223\215\344\275\234(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\350\275\273\346\211\253\346\223\215\344\275\234(ap).md" deleted file mode 100644 index 2a837fc53..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/List\345\210\227\350\241\250/List-\350\275\273\346\211\253\346\223\215\344\275\234(ap).md" +++ /dev/null @@ -1,29 +0,0 @@ - -你可以使用 `.swipeActions()` 修饰符来添加轻扫操作。以下是一个例子: - -```swift -struct ContentView: View { - @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - - var body: some View { - List { - ForEach(items, id: \.self) { item in - Text(item) - .swipeActions { - Button(action: { - // 这里是你的删除操作 - if let index = items.firstIndex(of: item) { - items.remove(at: index) - } - }) { - Label("Delete", systemImage: "trash") - } - .tint(.red) - } - } - } - } -} -``` - -在这个例子中,我们创建了一个包含五个元素的 List,并为每个元素添加了一个滑动操作。当用户向左轻扫一个元素时,会显示一个 "Delete" 按钮,用户可以点击这个按钮来删除该元素。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/ScrollView(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/ScrollView(ap).md" deleted file mode 100644 index d23335a0a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/ScrollView(ap).md" +++ /dev/null @@ -1,99 +0,0 @@ - -## 新增 modifier - -```swift -ScrollView { - ForEach(0..<300) { i in - Text("\(i)") - .id(i) - } -} -.scrollDisabled(false) // 设置是否可滚动 -.scrollDismissesKeyboard(.interactively) // 关闭键盘 -.scrollIndicators(.visible) // 设置滚动指示器是否可见 -``` - -## ScrollViewReader - -ScrollView 使用 scrollTo 可以直接滚动到指定的位置。ScrollView 还可以透出偏移量,利用偏移量可以定义自己的动态视图,比如向下向上滚动视图时有不同效果,到顶部显示标题视图等。 - -示例代码如下: - -```swift -struct PlayScrollView: View { - @State private var scrollOffset: CGFloat = .zero - - var infoView: some View { - GeometryReader { g in - Text("移动了 \(Double(scrollOffset).formatted(.number.precision(.fractionLength(1)).rounded()))") - .padding() - } - } - - var body: some View { - // 标准用法 - ScrollViewReader { s in - ScrollView { - ForEach(0..<300) { i in - Text("\(i)") - .id(i) - } - } - Button("跳到150") { - withAnimation { - s.scrollTo(150, anchor: .top) - } - } // end Button - } // end ScrollViewReader - - // 自定义的 ScrollView 透出 offset 供使用 - ZStack { - PCScrollView { - ForEach(0..<100) { i in - Text("\(i)") - } - } whenMoved: { d in - scrollOffset = d - } - infoView - - } // end ZStack - } // end body -} - -// MARK: - 自定义 ScrollView -struct PCScrollView: View { - let c: () -> C - let whenMoved: (CGFloat) -> Void - - init(@ViewBuilder c: @escaping () -> C, whenMoved: @escaping (CGFloat) -> Void) { - self.c = c - self.whenMoved = whenMoved - } - - var offsetReader: some View { - GeometryReader { g in - Color.clear - .preference(key: OffsetPreferenceKey.self, value: g.frame(in: .named("frameLayer")).minY) - } - .frame(height:0) - } - - var body: some View { - ScrollView { - offsetReader - c() - .padding(.top, -8) - } - .coordinateSpace(name: "frameLayer") - .onPreferenceChange(OffsetPreferenceKey.self, perform: whenMoved) - } // end body -} - -private struct OffsetPreferenceKey: PreferenceKey { - static var defaultValue: CGFloat = .zero - static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {} -} -``` - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/ScrollView-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/ScrollView-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" deleted file mode 100644 index 813674916..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/ScrollView-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" +++ /dev/null @@ -1,10 +0,0 @@ - -## 文档 - -- [ScrollView | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/scrollview) 官方文档 -- [Scroll views | 接口](https://developer.apple.com/documentation/swiftui/scroll-views) 官方接口文档 - -## WWDC - -23 -- [Beyond scroll views - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10159) diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/scrollTargetBehavior\345\210\206\351\241\265\346\273\232\345\212\250(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/scrollTargetBehavior\345\210\206\351\241\265\346\273\232\345\212\250(ap).md" deleted file mode 100644 index 3995f152e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/scrollTargetBehavior\345\210\206\351\241\265\346\273\232\345\212\250(ap).md" +++ /dev/null @@ -1,85 +0,0 @@ - -## 按可视尺寸分页 - -`.scrollTargetBehavior(.paging)` 可以让 ScrollView 滚动,滚动一页的范围是 ScrollView 的可视尺寸。 -  -```swift -struct ContentView: View { - var body: some View { - ScrollView(.horizontal) { - LazyHStack { - ForEach(0...20, id: \.self) { i in - colorView() - .frame(width: 300, height: 200) - } - } - } - .scrollTargetBehavior(.paging) - } - - @ViewBuilder - func colorView() -> some View { - [Color.red, Color.yellow, Color.blue, Color.mint, Color.indigo, Color.green].randomElement() - } -} -``` - -## 按容器元素对齐分页 - -使用 `.scrollTargetBehavior(.viewAligned)` 配合 scrollTargetLayout。示例代码如下: - -```swift -struct ContentView: View { - var body: some View { - ScrollView(.horizontal) { - LazyHStack { - ForEach(0...20, id: \.self) { i in - colorView() - .frame(width: 300, height: 200) - } - } - .scrollTargetLayout(isEnabled: true) - } - .scrollTargetBehavior(.viewAligned) - } - - @ViewBuilder - func colorView() -> some View { - [Color.red, Color.yellow, Color.blue, Color.mint, Color.indigo, Color.green].randomElement() - } -} -``` - -## containerRelativeFrame 控制划动单元显示范围 - -containerRelativeFrame 可以控制划动单元显示范围,可以控制单个内容一页,也可以控制多个内容一页。 - -以下是 containerRelativeFrame 的使用示例: - -```swift -struct ContentView: View { - - var body: some View { - ScrollView(.horizontal) { - HStack(spacing: 10.0) { - ForEach(0...20, id: \.self) { _ in - Image("evermore") - .resizable() - .scaledToFit() - .containerRelativeFrame(.horizontal, count: 4, span: 3, spacing: 8) - } - } - } - .scrollTargetBehavior(.paging) - .safeAreaPadding(.horizontal, 20.0) - } -} -``` - -以上代码中,我们使用 containerRelativeFrame 修饰符,将每个 Image 视图的宽度设置为容器宽度的 3/4。我们还设置了每个 Image 视图之间的间距为 8 点。 - - - - - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/scrollTransition\350\247\206\350\247\211\346\225\210\346\236\234(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/scrollTransition\350\247\206\350\247\211\346\225\210\346\236\234(ap).md" deleted file mode 100644 index 14f094577..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/scrollTransition\350\247\206\350\247\211\346\225\210\346\236\234(ap).md" +++ /dev/null @@ -1,117 +0,0 @@ - - -iOS 17 新推出 `.scrollTransition`,用于处理滚动时的动画。 - -`.transition` 用于视图插入和移除视图树时的动画。 - -`.scrollTransition` 会和滚动联合起来进行平滑的过渡动画处理。`.scrollTransition` 可以修改很多属性,比如大小,可见性还有旋转等。 - -`.scrollTransition` 可以针对不同阶段进行处理,目前有三个阶段: - -- `topLeading`: 视图进入 ScrollView 可见区域 -- `identity`: 在可见区域中 -- `bottomTrailing`: 视图离开 ScrollView 可见区域 - -```swift -struct ContentView: View { - var body: some View { - ScrollView(.horizontal) { - LazyHStack { - ForEach(0...20, id: \.self) { i in - colorView() - .frame(width: 300, height: 200) - .scrollTransition { content, phase in - content - .scaleEffect(phase.isIdentity ? 1 : 0.4) - } - } - } - } - } - - @ViewBuilder - func colorView() -> some View { - [Color.red, Color.yellow, Color.blue, Color.mint, Color.indigo, Color.green].randomElement() - } -} -``` - -使用阶段的值 - -```swift -.scrollTransition(.animated(.bouncy)) { content, phase in - content - .scaleEffect(phase.isIdentity ? 1 : phase.value) -} -``` - -比如在 ScrollView 中使用 `.scrollTransition` 时,可以使用 `topLeading` 和 `bottomTrailing` 两个阶段。下面的示例代码中,我们使用 `.scrollTransition` 为每个专辑封面添加了动画效果。 - -```swift -struct ContentView: View { - enum ImageNames: String, CaseIterable { - case speaknow, midnights, red, fearless, evermore, folklore, lover - } - - var body: some View { - ScrollView { - VStack(spacing: 16) { - ForEach(ImageNames.allCases, id: \.self) { album in - albumImage(album: album) - .padding(.horizontal, 16) - .scrollTransition( - topLeading: .animated(.smooth), - bottomTrailing: .animated(.smooth), - axis: .vertical, - transition: { content, phase in - content - .scaleEffect(1.0 - (abs(phase.value) * 0.05)) - .blur(radius: 2 * abs(phase.value)) - }) - } - } - } - } - - func albumImage(album: ImageNames) -> some View { - Image(album.rawValue) - .resizable() - .aspectRatio(1.0, contentMode: .fit) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .overlay( - Text(album.rawValue.capitalized) - .font(.title) - .foregroundColor(.white) - .shadow(color: .black, radius: 3, x: 0, y: 0), - alignment: .bottom - ) - } -} -``` - -以上代码中,我们使用了 `.scrollTransition` 为每个专辑封面添加了动画效果。在 `topLeading` 阶段,我们使用了 `.animated(.smooth)` 动画效果,使专辑封面在进入可见区域时平滑放大。在 `bottomTrailing` 阶段,我们使用了 `.animated(.smooth)` 动画效果,使专辑封面在离开可见区域时平滑缩小。 - -不同阶段的产生效果设置 - -```swift -.scrollTransition( - topLeading: .animated, - bottomTrailing: .interactive -) { content, phase in - content.rotationEffect(.radians(phase.value)) -} -``` - -`.rotation3DEffect` 也是支持的。 - -```swift -.scrollTransition(.interactive) { content, phase in - content - .rotation3DEffect( - Angle.degrees(phase.isIdentity ? 0: 120), - axis: (x: 0.9, y: 0.0, z: 0.1)) - .offset(x: phase.value * -300) -} -``` - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/\345\233\272\345\256\232\345\210\260\346\273\232\345\212\250\350\247\206\345\233\276\347\232\204\351\241\266\351\203\250(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/\345\233\272\345\256\232\345\210\260\346\273\232\345\212\250\350\247\206\345\233\276\347\232\204\351\241\266\351\203\250(ap).md" deleted file mode 100644 index 4e4f31883..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/\345\233\272\345\256\232\345\210\260\346\273\232\345\212\250\350\247\206\345\233\276\347\232\204\351\241\266\351\203\250(ap).md" +++ /dev/null @@ -1,17 +0,0 @@ - -LazyVStack 有个参数 pinnedViews 可以用于固定滚动视图的顶部。 - -```swift -ScrollView { - LazyVStack(alignment: .leading, spacing: 10, pinnedViews: .sectionHeaders) { - Section { - ForEach(books) { book in - BookRowView(book: book) - } - } header: { - HeaderView(title: "小说") - } - .... - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/\346\273\232\345\212\250\345\210\260\347\211\271\345\256\232\347\232\204\344\275\215\347\275\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/\346\273\232\345\212\250\345\210\260\347\211\271\345\256\232\347\232\204\344\275\215\347\275\256(ap).md" deleted file mode 100644 index 49783fd36..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Scroll\346\273\232\345\212\250\350\247\206\345\233\276/\346\273\232\345\212\250\345\210\260\347\211\271\345\256\232\347\232\204\344\275\215\347\275\256(ap).md" +++ /dev/null @@ -1,101 +0,0 @@ - - -## scrollPostion 版本 - -`scrollPositon(id:)` 比 ScrollViewReader 简单,但是只适用于 ScrollView。数据源遵循 Identifiable,不用显式使用 `id` 修饰符 - -```swift -struct ContentView: View { - @State private var id: Int? - - var body: some View { - VStack { - Button("Scroll to Bookmark 3") { - withAnimation { - id = 3 - } - } - Button("Scroll to Bookmark 13") { - withAnimation { - id = 13 - } - } - ScrollView { - ScrollViewReader { scrollView in - LazyVStack { - ForEach(Bookmark.simpleData()) { bookmark in - Text("\(bookmark.index)") - .id(bookmark.index) - } - - } - } - } - .scrollPosition(id: $id) - .scrollTargetLayout() - } - } - - struct Bookmark: Identifiable,Hashable { - let id = UUID() - let index: Int - - static func simpleData() -> [Bookmark] { - var re = [Bookmark]() - for i in 0...100 { - re.append(Bookmark(index: i)) - } - return re - } - } -} -``` - -scrollTargetLayout 可以获得当前滚动位置。锚点不可配,默认是 center。 - - -## ScrollViewReader 版本 - -ScrollViewReader 这个版本可以适用于 List,也可以配置锚点 - -你可以使用 `ScrollViewReader` 和 `scrollTo(_:anchor:)` 方法来滚动到特定的元素。以下是一个例子: - -```swift -struct ContentView: View { - var bookmarks: [Int] = Array(1...100) - @State private var selectedBookmarkId: Int? - - var body: some View { - VStack { - Button("Scroll to Bookmark 3") { - selectedBookmarkId = 3 - } - Button("Scroll to Bookmark 13") { - selectedBookmarkId = 13 - } - ScrollView { - ScrollViewReader { scrollView in - LazyVStack { - ForEach(bookmarks.indices, id: \.self) { index in - Text("\(bookmarks[index])") - .id(index) - } - .onChange(of: selectedBookmarkId) { oldValue, newValue in - if let newValue = newValue { - withAnimation { - scrollView.scrollTo(newValue, anchor: .top) - } - } - } - } - } - } - } - } -} -``` - -在这个例子中,我们首先创建了一个 `Button`,当点击这个按钮时,`selectedBookmarkId` 的值会被设置为 3。然后,我们创建了一个 `ScrollView`,并在 `ScrollView` 中添加了一个 `ScrollViewReader`。我们在 `ScrollViewReader` 中添加了一个 `LazyVStack`,并使用 `ForEach` 遍历 `bookmarks` 数组的索引,为每个索引创建一个 `Text` 视图。我们使用 `id(_:)` 方法为每个 `Text` 视图设置了一个唯一的 ID。 - -我们使用 `onChange(of:perform:)` 方法来监听 `selectedBookmarkId` 的变化。当 `selectedBookmarkId` 的值改变时,我们会调用 `scrollTo(_:anchor:)` 方法来滚动到特定的元素。`anchor: .top` 参数表示我们希望滚动到的元素位于滚动视图的顶部。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table(ap).md" deleted file mode 100644 index 075473b1f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table(ap).md" +++ /dev/null @@ -1,53 +0,0 @@ - - - -今年 iOS 和 iPadOS 也可以使用去年只能在 macOS 上使用的 Table了,据 digital lounges 里说,iOS table 的性能和 list 差不多,table 默认为 plian list。我想 iOS 上加上 table 只是为了兼容 macOS 代码吧。 - -table 使用示例如下: -```swift -struct ContentView: View { - var body: some View { - Table(Fruit.simpleData()) { - TableColumn("名字", value: \.name) - TableColumn("颜色", value: \.color) - TableColumn("颜色") { - Text("\($0.name)") - .font(.footnote) - .foregroundStyle(.cyan) - } - } - .contextMenu(forSelectionType: Fruit.ID.self) { selection in - if selection.isEmpty { - Button("添加") { - // ... - } - } else if selection.count == 1 { - Button("收藏") { - // ... - } - } else { - Button("收藏多个") { - // ... - } - } - } - } - - struct Fruit:Identifiable { - let id = UUID() - let name: String - let color: String - - static func simpleData() -> [Fruit] { - var re = [Fruit]() - re.append(Fruit(name: "Apple", color: "Red")) - re.append(Fruit(name: "Banana", color: "Yellow")) - re.append(Fruit(name: "Cherry", color: "Red")) - re.append(Fruit(name: "Date", color: "Brown")) - re.append(Fruit(name: "Elderberry", color: "Purple")) - return re - } - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-contextMenu(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-contextMenu(ap).md" deleted file mode 100644 index a13dbae7c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-contextMenu(ap).md" +++ /dev/null @@ -1,30 +0,0 @@ - -```swift -struct ContentView: View { - @State private var selection: Set = [] - var body: some View { - Table(Fruit.simpleData(), selection: $selection) { - ... - } - .contextMenu(forSelectionType: Fruit.ID.self) { selection in - if selection.isEmpty { - Button("添加") { - // ... - } - } else if selection.count == 1 { - Button("收藏") { - // ... - } - } else { - Button("收藏多个") { - // ... - } - } - } primaryAction: { items in - // 双击某一行时 - debugPrint(items) - } - } - ... -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\345\244\232\345\261\236\346\200\247\346\216\222\345\272\217(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\345\244\232\345\261\236\346\200\247\346\216\222\345\272\217(ap).md" deleted file mode 100644 index cf13f85b0..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\345\244\232\345\261\236\346\200\247\346\216\222\345\272\217(ap).md" +++ /dev/null @@ -1,61 +0,0 @@ - -你可以使用 `Table` 视图的 `sortOrder` 参数来实现多属性排序。`sortOrder` 参数接受一个绑定到一个 `SortDescriptor` 数组的变量,这个数组定义了排序的顺序和方式。 - -以下是一个使用 `Table` 视图实现多属性排序的例子: - -```swift -struct ContentView: View { - @State private var sortOrder: [KeyPathComparator] = [.init(\.name, order: .reverse)] - - @State var data = [ - Fruit(name: "Apple", color: "Red"), - Fruit(name: "Banana", color: "Yellow"), - Fruit(name: "Cherry", color: "Red"), - Fruit(name: "Date", color: "Brown"), - Fruit(name: "Elderberry", color: "Purple") - ] - - var body: some View { - sortKeyPathView() // 排序状态 - Table(data, sortOrder: $sortOrder) { - TableColumn("Fruit", value: \.name) - TableColumn("Color", value: \.color) - // 不含 value 参数的不支持排序 - TableColumn("ColorNoOrder") { - Text("\($0.color)") - .font(.footnote) - .foregroundStyle(.mint) - } - } - .task { - data.sort(using: sortOrder) - } - .onChange(of: sortOrder) { oldValue, newValue in - data.sort(using: newValue) - } - .padding() - } - - @ViewBuilder - func sortKeyPathView() -> some View { - HStack { - ForEach(sortOrder, id: \.self) { order in - Text(order.keyPath == \Fruit.name ? "名字" : "颜色") - Image(systemName: order.order == .reverse ? "chevron.down" : "chevron.up") - } - } - .padding(.top) - } -} - -struct Fruit: Identifiable { - let id = UUID() - let name: String - let color: String -} -``` - -在这个例子中,我们首先定义了一个 `@State` 变量 `sortOrder`,它是一个 `SortDescriptor` 数组,定义了排序的顺序和方式。然后,我们将这个变量绑定到 `Table` 视图的 `sortOrder` 参数。 - -现在,当用户点击表头来排序一个列时,`sortOrder` 变量就会被更新。你可以使用这个变量来实现多属性排序,或者实现其他的交互功能。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\346\240\267\345\274\217(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\346\240\267\345\274\217(ap).md" deleted file mode 100644 index fbd260d80..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\346\240\267\345\274\217(ap).md" +++ /dev/null @@ -1,18 +0,0 @@ - -在 SwiftUI 中,`Table` 视图的 `.tableStyle` 修改器可以用来设置表格的样式。目前,SwiftUI 提供了以下几种表格样式: - -- inset:默认 -- `inset(alternatesRowBackgrounds: Bool)`:是否开启行交错背景 -- bordered:加边框 -- `bordered(alternatesRowBackgrounds: Bool)`: 是否开启行交错背景 - -你可以使用 `.tableStyle` 修改器来设置表格的样式,例如: - -```swift -Table(data) { - // ... -} -.tableStyle(InsetGroupedListStyle()) -``` - -这段代码会将表格的样式设置为 `InsetGroupedListStyle`。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\350\241\214\347\232\204\351\200\211\346\213\251(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\350\241\214\347\232\204\351\200\211\346\213\251(ap).md" deleted file mode 100644 index 697da9ce7..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\225\260\346\215\256\351\233\206\345\220\210\347\273\204\344\273\266/Table\350\241\250\346\240\274/Table-\350\241\214\347\232\204\351\200\211\346\213\251(ap).md" +++ /dev/null @@ -1,41 +0,0 @@ - -你可以使用 Table 视图的 selection 参数来实现单选和多选。selection 参数接受一个绑定到一个可选的 Set 的变量,这个 Set 包含了被选中的元素的标识。 - -以下是一个使用 Table 视图实现单选和多选的例子: - -```swift -struct ContentView: View { - @State private var selectionOne: UUID? // 单选 - @State private var selection: Set = [] // 多选 - - let data = [ - Fruit(name: "Apple", color: "Red"), - Fruit(name: "Banana", color: "Yellow"), - Fruit(name: "Cherry", color: "Red"), - Fruit(name: "Date", color: "Brown"), - Fruit(name: "Elderberry", color: "Purple") - ] - - var body: some View { - Table(data, selection: $selectionOne) { - TableColumn("Fruit") { item in - Text(item.name) - } - TableColumn("Color") { item in - Text(item.color) - } - } - } -} - -struct Fruit: Identifiable { - let id = UUID() - let name: String - let color: String -} -``` - -在这个例子中,我们首先定义了一个 @State 变量 selection,它是一个 Set,包含了被选中的元素的标识。然后,我们将这个变量绑定到 Table 视图的 selection 参数。 - -现在,当用户选择或取消选择一个元素时,selection 变量就会被更新。你可以使用这个变量来判断哪些元素被选中,或者实现其他的交互功能。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Alert(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Alert(ap).md" deleted file mode 100644 index 7c0b80a31..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Alert(ap).md" +++ /dev/null @@ -1,27 +0,0 @@ - -`.alert` 是一个用于显示警告对话框的修饰符。以下是一个使用 `.alert` 的例子 - -```swift -struct ContentView: View { - @State private var showAlert = false - @State private var authCode = "" - - var body: some View { - Button("删除漫画") { - showAlert = true - } - .alert("确定要删除这部漫画吗?", isPresented: $showAlert) { - Button("删除", role: .destructive) { - print("漫画已删除") - } - Button("取消", role: .cancel) { - print("取消删除") - } - TextField("enter", text: $authCode) - } message: { - Text("请输入验证码?") - } - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Full Screen Modal View(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Full Screen Modal View(ap).md" deleted file mode 100644 index 9bb8ac730..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Full Screen Modal View(ap).md" +++ /dev/null @@ -1,43 +0,0 @@ - -`fullScreenCover` 是一个非常有用的修饰符,它可以让你全屏显示一个视图。以下是一个使用 `fullScreenCover` 的例子 - -```swift -import SwiftUI - -struct ContentView: View { - @State private var isPresented = false - - var body: some View { - Button(action: { - isPresented = true - }) { - Text("显示电影详情") - } - .fullScreenCover(isPresented: $isPresented) { - MovieDetailView() - } - } -} - -struct MovieDetailView: View { - @Environment(\.dismiss) var dismiss - - var body: some View { - VStack { - Text("电影标题") - .font(.largeTitle) - Text("电影详情") - .font(.body) - Button(action: { - // Dismiss the view - dismiss() - }) { - Text("关闭") - } - } - } -} -``` - -在这个例子中,我们创建了一个 `ContentView` 视图,其中包含一个按钮。当用户点击这个按钮时,我们将 `isPresented` 设置为 `true`,这将触发 `fullScreenCover` 并显示 `MovieDetailView` 视图。在 `MovieDetailView` 视图中,我们显示电影的标题和详情,以及一个可以关闭视图的按钮。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/HUD(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/HUD(ap).md" deleted file mode 100644 index 488a2f7ea..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/HUD(ap).md" +++ /dev/null @@ -1,40 +0,0 @@ - -HUD 是 Heads-Up Display 的缩写,翻译为中文是"平视显示器"。在编程中,HUD 通常指的是一种特殊的视图或窗口,它会浮动在应用程序的主界面之上,用于显示某些重要的、临时的信息,比如加载状态、提示信息等。 - -在 SwiftUI 中,创建一个自定义的 HUD 可以通过创建一个新的 View 来实现。以下是一个简单的 HUD 示例: - -```swift -struct HUDView: View { - var body: some View { - ZStack { - VStack { - ProgressView() - Text("加载中...") - .padding(.top, 20) - } - .foregroundColor(.white) - .frame(width: 120, height: 120) - .background(Color.indigo) - .cornerRadius(16) - } - } -} - -struct ContentView: View { - @State private var showHUD = false - - var body: some View { - VStack { - Button("显示 HUD") { - showHUD = true - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - showHUD = false - } - } - } - .overlay(showHUD ? HUDView() : nil) - } -} -``` - -在这个例子中,我们创建了一个 `HUDView` 视图,它包含一个 `ProgressView` 和一个 `Text`。我们还创建了一个 `ContentView` 视图,其中包含一个按钮,当点击按钮时,会显示 `HUDView`。通过 `DispatchQueue.main.asyncAfter` 方法,我们在 2 秒后隐藏 `HUDView`。 \ No newline at end of file diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Menu\345\222\214ContextMenu(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Menu\345\222\214ContextMenu(ap).md" deleted file mode 100644 index c69fe4623..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Menu\345\222\214ContextMenu(ap).md" +++ /dev/null @@ -1,78 +0,0 @@ - -## Menu 视图 - -`Menu` 视图可以用来创建一个菜单,比如创建相册或文件夹时的下拉选项。用户可以从中选择一个选项。以下是一个使用 `Menu` 的例子,其中包含一个子菜单: - -```swift -struct ContentView: View { - @State private var selectedComic = "漫画1" - - var body: some View { - VStack { - Text("当前选择的漫画是:\(selectedComic)") - .padding() - - Menu { - Button(action: { selectedComic = "漫画1" }) { - Text("漫画1") - } - - Button(action: { selectedComic = "漫画2" }) { - Text("漫画2") - } - - Menu { - Button(action: { selectedComic = "漫画3" }) { - Text("漫画3") - } - - Button(action: { selectedComic = "漫画4" }) { - Text("漫画4") - } - } label: { - Label("更多", systemImage: "folder.circle") - } - } label: { - Label("选择漫画", systemImage: "ellipsis.circle") - } - .menuIndicator(.hidden) - .menuOrder(.fixed) // 保持菜单列表顺序 - - } - } -} -``` - -## `.contextMenu` - -`Menu` 视图可以用来创建一个菜单,用户可以从中选择一个选项。以下是一个使用 `Menu` 的例子: - -```swift -struct Comic: Identifiable,Hashable { - let id = UUID() - let name: String -} - -struct ContentView: View { - let comics = [Comic(name: "漫画1"), Comic(name: "漫画2"), Comic(name: "漫画3")] - - var body: some View { - Table(comics) { - TableColumn("漫画名称", value: \.name) - } - .contextMenu(forSelectionType: Comic.ID.self) { comics in - ControlGroup { - Button("添加到收藏") { } - Button("分享") { } - } - - Button(role: .destructive) { - // 删除动作 - } label: { - Label("删除", systemImage: "trash") - } - } - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Popover(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Popover(ap).md" deleted file mode 100644 index 86dd0ea87..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Popover(ap).md" +++ /dev/null @@ -1,44 +0,0 @@ - - -`.popover` 是一个用于显示弹出视图的修饰符。以下是一个使用 `.popover` 的例子 - -```swift -import SwiftUI - -struct ContentView: View { - @State private var showPopover = false - - var body: some View { - Button("显示漫画详情") { - showPopover = true - } - .popover(isPresented: $showPopover, - attachmentAnchor: .point(.bottom), - arrowEdge: .bottom - ) { - VStack { - Text("漫画标题") - .font(.title) - Text("漫画详情") - .font(.body) - Button("关闭") { - showPopover = false - } - .padding() - } - .padding() - .presentationCompactAdaptation(.popover) - } - } -} -``` - -代码中 `PresentationAdaptation` 的可选值: - -- `automatic`:默认适配方式 -- `none`:不进行尺寸等级适应 -- `popover`:优先使用弹出视图 -- `sheet`:优先使用工作表,适用于调整大小类(如 iPhone 默认) -- `fullScreenCover`:优先使用全屏覆盖视图 - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Sheet(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Sheet(ap).md" deleted file mode 100644 index 9dd0db98c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/Sheet(ap).md" +++ /dev/null @@ -1,373 +0,0 @@ - - -## 绑定到布尔值 - -下面代码示例的这个视图允许用户通过点击按钮来显示和隐藏详情。 - -```swift -struct DetailView: View { - @Environment(\.dismiss) var close - - var body: some View { - Button("点击关闭详情") { - close() - } - .font(.headline) - .padding() - .background(Color.blue) - .foregroundColor(.white) - } -} - -struct MainView: View { - @State private var showDetail = false - - var body: some View { - Button("显示详情") { - showDetail.toggle() - } - .sheet(isPresented: $showDetail) { - DetailView() - } - } -} -``` - -在 `MainView` 中,我们定义了一个状态变量 `showDetail`,并将其初始化为 `false`。然后,我们创建了一个 `Button` 视图,当点击按钮时,`showDetail` 的值会切换。我们还添加了一个 `sheet` 修饰符,当 `showDetail` 为 `true` 时,会显示 `DetailView`。 - -`DetailView` 视图中会调用 `close` 方法来关闭详情。 - -用 `fullScreenCover()` 修饰符视图就会全屏显示, 会让向下拖动来关闭 Sheet 功能失效。另外,新增的 `interactiveDismissDisabled` 修饰符也可以用来禁用用户通过向下滑动来关闭 Sheet。 - - -## 绑定到所选项目 - -```swift -struct ContentView: View { - @State private var selectedBook: Book? = nil - - var body: some View { - List(Book.simpleData(), selection: $selectedBook) { book in - BookRow(book: book) - .tag(book) - } - .sheet(item: $selectedBook, - onDismiss: { print("查看结束!") }, - content: { BookDetailView(book: $0) - }) - } -} - -struct BookRow: View { - let book: Book - - var body: some View { - HStack { - Text(book.title) - .font(.headline) - Spacer() - Text(book.author) - .font(.subheadline) - .foregroundColor(.gray) - } - } -} - -struct BookDetailView: View { - let book: Book - - var body: some View { - VStack { - Text(book.title) - .font(.largeTitle) - Text(book.author) - .font(.title) - .foregroundColor(.gray) - Text("评分: \(book.rating.rate, specifier: "%.1f") (\(book.rating.count) 人评价)") - .font(.subheadline) - } - .padding() - } -} - -struct Book: Identifiable, Hashable { - let id: UUID = UUID() - let title: String - let author: String - let cover: String - let rating: Rating - - static func simpleData() -> [Book] { - return [ - Book( - title: "斗罗大陆", - author: "唐家三少", - cover: "https://example.com/img/douluo.jpg", - rating: Rating(rate: 4.8, count: 2000) - ), - Book( - title: "盗墓笔记", - author: "南派三叔", - cover: "https://example.com/img/daomu.jpg", - rating: Rating(rate: 4.7, count: 1800) - ), - Book( - title: "鬼吹灯", - author: "天下霸唱", - cover: "https://example.com/img/guichui.jpg", - rating: Rating(rate: 4.6, count: 1900) - ), - Book( - title: "三体", - author: "刘慈欣", - cover: "https://example.com/img/santi.jpg", - rating: Rating(rate: 4.9, count: 2100) - ), - Book( - title: "神雕侠侣", - author: "金庸", - cover: "https://example.com/img/shendiao.jpg", - rating: Rating(rate: 4.8, count: 2200) - ) - ] - } -} - -struct Rating: Hashable { - let rate: Double - let count: Int -} -``` - -在上面的代码中,我们创建了一个 `Book` 结构体,它包含了书籍的标题、作者、封面图片和评分。我们还创建了一个 `Rating` 结构体,用于表示书籍的评分。然后,我们创建了一个 `BookRow` 视图,用于显示书籍的标题和作者。在 `ContentView` 中,我们创建了一个 `List` 视图,用于显示书籍列表。我们使用 `selection` 修饰符来绑定所选的书籍。然后,我们使用 `sheet(item:onDismiss:content:)` 修饰符来显示所选书籍的详细信息。在 `BookDetailView` 中,我们显示了书籍的标题、作者和评分。 - - - -## presentationDetents - -SwiftUI 新推出的 `presentationDetents()` modifier 可以创建一个可以定制的 bottom sheet。示例代码如下: - -```swift -struct ContentView: View { - @State private var books = Book.simpleData() - @State private var selectedBook: Book? = nil - @State private var currentDetent = PresentationDetent.large - - var body: some View { - List(books, selection: $selectedBook) { book in - BookRow(book: book) - .tag(book) - } - .sheet(item: $selectedBook, - onDismiss: { print("查看结束!") }, - content: { book in - BookDetailView(book: book) - .presentationDetents([.medium, .large, .fraction(0.8), .height(200)], - selection: $currentDetent) - }) - } -} -``` - -detent 默认值是 `.large`。也可以提供一个百分比,比如 `.presentationDetents([.fraction(0.7)])`,或者直接指定高度 `.presentationDetents([.height(100)])`。 - -以下是 detents 的配置: -- large: 全高 -- medium: 半高 -- fraction: 给出当前屏幕高度的百分比 -- height: 给出绝对高度(以磅为单位) - -presentationDragIndicator modifier 可以用来显示隐藏拖动标识。 - -更多 Detents 功能配置,参看下面例子 - -这是一个“显示音乐列表”的视图,包含一个自定义的 `MusicListView`。这个视图允许用户通过点击列表中的音乐来显示音乐的详细信息。 - -```swift -struct ContentView: View { - @State private var showingMusicSheet = true - - var body: some View { - MusicPlayerView() - .sheet(isPresented: $showingMusicSheet) { - ScrollView { - // 在这里添加底部 Sheet 的内容 - } - .interactiveDismissDisabled() - .presentationDetents([.height(100), .medium, .large]) - .presentationBackgroundInteraction( - .enabled(upThrough: .medium) - ) - } - } -} -``` - -在 `ContentView` 中,我们定义了一个状态变量 `showingMusicSheet`,并设置为 `true`,表示默认显示音乐列表。 - -然后,我们创建了一个 `MusicPlayerView` 视图,当 `showingMusicSheet` 为 `true` 时,会显示一个底部 Sheet ,该 Sheet 包含一个 `ScrollView`,你可以在其中添加你想要显示的内容。 - -我们使用了 `.interactiveDismissDisabled()` 修饰符来禁止用户通过向下滑动来关闭 Sheet 。我们使用了 `.presentationDetents([.height(100), .medium, .large])` 修饰符来设置 Sheet 的高度,用户可以通过向上和向下拖动来改变 Sheet 的高度。我们还使用了 `.presentationBackgroundInteraction(.enabled(upThrough: .medium))` 修饰符,允许用户在 Sheet 未完全打开时与父视图进行交互。 - -## 外观样式 - -presentationBackground 修饰符可以用来设置 Sheet 的背景颜色。示例代码如下: - -```swift -.sheet(isPresented: $showMusicPlayerSettings) { - MusicPlayerSettingsView() - .presentationBackground(alignment: .bottom) { - LinearGradient(colors: [Color.blue, Color.green], startPoint: .bottomLeading, endPoint: .topTrailing) - } -} -``` - -改变背景颜色: - -```swift -.presentationBackground(.red) -``` - -还可以使用材质: - -```swift -.presentationBackground(.ultraThinMaterial) -``` - -设置背景: - -```swift -.presentationBackground { - Image("someCoverImage") -} -``` - -iOS 16+ 和 macOS 13+ 新增 presentationCornerRadius 修饰符。这个修饰符可以用来设置 sheet 视图的圆角半径。示例代码如下: - -```swift -struct ContentView: View { - @State private var isShowingShoppingCart = false - - var body: some View { - Button(action: { - isShowingShoppingCart = true - }) { - Text("查看购物车") - } - .sheet(isPresented: $isShowingShoppingCart) { - ShoppingCartDetailView() - .presentationBackground(alignment: .bottom) { - LinearGradient(colors: [Color.orange, Color.pink], startPoint: .bottomLeading, endPoint: .topTrailing) - } - .presentationCornerRadius(20) - } - } -} - -struct ShoppingCartDetailView: View { - // 假设我们有一个购物车模型,其中包含商品列表 - @State private var cartItems = ["商品1", "商品2", "商品3"] - - var body: some View { - VStack { - Text("购物车详情") - .font(.title) - .padding() - - List(cartItems, id: \.self) { item in - Text(item) - } - .scrollContentBackground(.hidden) - - Text("总价: \(calculateTotalPrice())") - .font(.title2) - .padding() - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.clear) - .edgesIgnoringSafeArea(.all) - } - - // 计算购物车中所有商品的总价 - func calculateTotalPrice() -> Int { - // 这里我们只是返回一个固定的值作为示例 - return 100 - } -} -``` - -## 添加工具栏按钮 - -```swift -.toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("关闭", systemImage: "xmark") { - showSheet = false - } - } -} -``` - - -## 多 Sheet - -以下是一个示例,展示了如何在 SwiftUI 中使用多个 Sheet。在这个示例中,我们创建了一个 ContentView 视图,其中包含两个按钮,一个用于显示用户信息,另一个用于显示应用配置。当用户点击按钮时,会显示相应的 Sheet。 - -```swift -struct ContentView: View { - enum ModalType: String, Identifiable { - case userInfo, appConfig - var id: String { rawValue } - } - - @State private var selectedModal: ModalType? - - var body: some View { - VStack { - Button("查看用户信息", action: { selectedModal = .userInfo }) - Button("查看应用配置", action: { selectedModal = .appConfig }) - } - .sheet(item: $selectedModal, content: createModalView) - } - - @ViewBuilder - func createModalView(_ modalType: ModalType) -> some View { - switch modalType { - case .userInfo: - UserInfoView() - case .appConfig: - AppConfigView() - } - } -} - -struct UserInfoView: View { - var body: some View { - VStack { - Text("用户信息") - .font(.title) - .padding() - - // 在这里添加更多的用户信息 - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.white) - .edgesIgnoringSafeArea(.all) - } -} - -struct AppConfigView: View { - var body: some View { - VStack { - Text("应用配置") - .font(.title) - .padding() - - // 在这里添加更多的应用配置信息 - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.white) - .edgesIgnoringSafeArea(.all) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/confirmationDialog()(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/confirmationDialog()(ap).md" deleted file mode 100644 index 82827015a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/confirmationDialog()(ap).md" +++ /dev/null @@ -1,34 +0,0 @@ - -在 SwiftUI 中,`confirmationDialog()` 是一个用于显示确认对话框的修饰符。支持 iOS 15 或更高版本,也适用于 macOS。以下是一个使用 `confirmationDialog()` 的例子 - -```swift -import SwiftUI - -struct ContentView: View { - @State private var showingConfirmation = false - - var body: some View { - Button("删除漫画") { - showingConfirmation = true - } - .confirmationDialog("确定要删除这部漫画吗?", - isPresented: $showingConfirmation, - titleVisibility: .hidden - ) { - Button("删除", role: .destructive) { - print("漫画已删除") - } - .keyboardShortcut(.defaultAction) // 确保始终会在顶部 - Button("取消", role: .cancel) { - print("取消删除") - } - } message: { - Text("删除操作无法恢复,你确定继续删除么?") - } - } -} -``` - -使用 `.dialogSuppressionToggle(isSuppressed: $isSuppressed)` 能够在 macOS 上添加一个复选框,允许用户选择不再显示确认对话框。 - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/\346\265\256\345\261\202(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/\346\265\256\345\261\202(ap).md" deleted file mode 100644 index ceb465bf8..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\346\265\256\345\261\202\347\273\204\344\273\266/\346\265\256\345\261\202(ap).md" +++ /dev/null @@ -1,156 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/uplayer-ap01.png) - -浮层有 HUD、ContextMenu、Sheet、Alert、ConfirmationDialog、Popover、ActionSheet 等几种方式。这些方式实现代码如下: - -```swift -struct PlaySuperposedLayerView: View { - @StateObject var hudVM = PHUDVM() - @State private var isShow = false - @State private var isShowAlert = false - @State private var isShowConfirmationDialog = false - @State private var isShowPopover = false - - var body: some View { - VStack { - - - List { - ForEach(0..<100) { i in - Text("\(i)") - .contextMenu { - // 在 macOS 上右键会出现的菜单 - Button { - print("\(i) is clicked") - } label: { - Text("Click \(i)") - } - } - } - } - .navigationTitle("列表") - .toolbar { - ToolbarItemGroup(placement: .automatic) { - Button("查看 Sheet") { - isShow = true - } - - Button("查看 Alert") { - isShowAlert = true - } - - Button("查看 confirmationDialog", role: .destructive) { - isShowConfirmationDialog = true - } - - // Popover 样式默认是弹出窗口置于按钮上方,指向底部。 - Button("查看 Popover") { - isShowPopover = true - } - .popover(isPresented: $isShowPopover, attachmentAnchor: .point(.trailing), arrowEdge: .trailing) { - Text("Popover 的内容") - .padding() - } - - } // end ToolbarItemGroup - } // end toolbar - .alert(isPresented: $isShowAlert) { - Alert(title: Text("弹框标题"), message: Text("弹框内容")) - } - .sheet(isPresented: $isShow) { - print("dismiss") - } content: { - VStack { - Label("Sheet", systemImage: "brain.head.profile") - Button("关闭") { - isShow = false - } - } - .padding(20) - } - .confirmationDialog("确定删除?", isPresented: $isShowConfirmationDialog, titleVisibility: .hidden) { - Button("确定") { - // do good thing - } - .keyboardShortcut(.defaultAction) // 使用 keyboardShortcut 可以设置成为默认选项样式 - - Button("不不", role: .cancel) { - // good choice - } - - } message: { - Text("这个东西还有点重要哦") - } - - Button { - hudVM.show(title: "您有一条新的短消息", systemImage: "ellipsis.bubble") - } label: { - Label("查看 HUD", systemImage: "switch.2") - } - .padding() - } - .environmentObject(hudVM) - .hud(isShow: $hudVM.isShow) { - Label(hudVM.title, systemImage: hudVM.systemImage) - } - } -} - -// MARK: - 供全局使用的 HUD -final class PHUDVM: ObservableObject { - @Published var isShow: Bool = false - var title: String = "" - var systemImage: String = "" - - func show(title: String, systemImage: String) { - self.title = title - self.systemImage = systemImage - withAnimation { - isShow = true - } - } -} - -// MARK: - 扩展 View 使其能够有 HUD 的能力 -extension View { - func hud( - isShow: Binding, - @ViewBuilder v: () -> V - ) -> some View { - ZStack(alignment: .top) { - self - - if isShow.wrappedValue == true { - PHUD(v: v) - .transition(AnyTransition.move(edge: .top).combined(with: .opacity)) - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - withAnimation { - isShow.wrappedValue = false - } - } - } - .zIndex(1) - .padding() - } - } - } -} - -// MARK: - 自定义 HUD -struct PHUD: View { - @ViewBuilder let v: V - - var body: some View { - v - .padding() - .foregroundColor(.black) - .background( - Capsule() - .foregroundColor(.white) - .shadow(color: .black.opacity(0.2), radius: 12, x: 0, y: 5) - ) - } -} -``` - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Form(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Form(ap).md" deleted file mode 100644 index 03aa45f6e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Form(ap).md" +++ /dev/null @@ -1,137 +0,0 @@ - - - -| 控件视图 | 说明 | Style | -| --- | --- | --- | -| Button | 触发操作的按钮 | .bordered, .borderless, .borderedProminent, .plain | -| Picker | 提供多选项供选择 | .wheel, .inline, .segmented, .menu, .radioGroup | -| DatePicker and MultiDatePicker | 选择日期的工具 | .compact, .wheel, .graphical | -| Toggle | 切换两种状态的开关 | .switch, .botton, .checkbox | -| Stepper | 调整数值的步进器 | 无样式选项 | -| Menu | 显示选项列表的菜单 | .borderlessButton, .button | - -Form 有 ColumnFormStyle 还有 GroupedFormStyle。使用 buttonStyle 修饰符: -```swift -Form { - ... -}.formStyle(.grouped) -``` - -Form 新版也得到了增强,示例如下: - -```swift -struct SimpleFormView: View { - @State private var date = Date() - @State private var eventDescription = "" - @State private var accent = Color.red - @State private var scheme = ColorScheme.light - - var body: some View { - Form { - Section { - DatePicker("Date", selection: $date) - TextField("Description", text: $eventDescription) - .lineLimit(3) - } - - Section("Vibe") { - Picker("Accent color", selection: $accent) { - ForEach(Color.accentColors, id: \.self) { color in - Text(color.description.capitalized).tag(color) - } - } - Picker("Color scheme", selection: $scheme) { - Text("Light").tag(ColorScheme.light) - Text("Dark").tag(ColorScheme.dark) - } - } - } - .formStyle(.grouped) - } -} - -extension Color { - static let accentColors: [Color] = [.red, .green, .blue, .orange, .pink, .purple, .yellow] -} -``` - -Form 的样式除了 `.formStyle(.grouped)` 还有 `.formStyle(..columns)`。 - -关于 Form 字体、单元、背景颜色设置,参看下面代码: - -```swift -struct ContentView: View { - @State private var movieTitle = "" - @State private var isWatched = false - @State private var rating = 1 - @State private var watchDate = Date() - - var body: some View { - Form { - Section { - TextField("电影标题", text: $movieTitle) - LabeledContent("导演", value: "克里斯托弗·诺兰") - } header: { - Text("关于电影") - } - .listRowBackground(Color.gray.opacity(0.1)) - - Section { - Toggle("已观看", isOn: $isWatched) - Picker("评分", selection: $rating) { - ForEach(1...5, id: \.self) { number in - Text("\(number) 星") - } - } - - } header: { - Text("电影详情") - } - .listRowBackground(Color.gray.opacity(0.1)) - - Section { - DatePicker("观看日期", selection: $watchDate) - } - .listRowBackground(Color.gray.opacity(0.1)) - - Section { - Button("重置所有电影数据") { - resetAllData() - } - } - .listRowBackground(Color.white) - } - .foregroundColor(.black) - .tint(.indigo) - .background(Color.yellow) - .scrollContentBackground(.hidden) - .navigationBarTitle("电影追踪器") - } - - private func resetAllData() { - movieTitle = "" - isWatched = false - rating = 1 - watchDate = Date() - } -} - -struct LabeledContent: View { - let label: String - let value: String - - init(_ label: String, value: String) { - self.label = label - self.value = value - } - - var body: some View { - HStack { - Text(label) - Spacer() - Text(value) - } - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/ColorPicker(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/ColorPicker(ap).md" deleted file mode 100644 index 165b2be92..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/ColorPicker(ap).md" +++ /dev/null @@ -1,22 +0,0 @@ - - -`ColorPicker` 是一个允许用户选择颜色的视图。以下是一个 `ColorPicker` 的使用示例: - -```swift -import SwiftUI - -struct ContentView: View { - @State private var selectedColor = Color.white - - var body: some View { - VStack { - ColorPicker("选择一个颜色", selection: $selectedColor) - Text("你选择的颜色") - .foregroundColor(selectedColor) - } - } -} -``` - -在这个示例中,我们创建了一个 `ColorPicker` 视图,用户可以通过这个视图选择一个颜色。我们使用 `@State` 属性包装器来创建一个可以绑定到 `ColorPicker` 的 `selectedColor` 状态。当用户选择一个新的颜色时,`selectedColor` 状态会自动更新,`Text` 视图的前景色也会相应地更新。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/DatePicker(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/DatePicker(ap).md" deleted file mode 100644 index 3f4707c40..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/DatePicker(ap).md" +++ /dev/null @@ -1,62 +0,0 @@ - - -## 基本使用 - -```swift -struct ContentView: View { - @State private var releaseDate: Date = Date() - - var body: some View { - VStack(spacing: 30) { - DatePicker("选择电影发布日期", selection: $releaseDate, displayedComponents: .date) - Text("选择的发布日期: \(releaseDate, formatter: DateFormatter.dateMedium)") - } - .padding() - } -} -``` - - -## 选择多个日期 - -在 iOS 16 中,您现在可以允许用户选择多个日期,MultiDatePicker 视图会显示一个日历,用户可以选择多个日期,可以设置选择范围。示例如下: -```swift -struct PMultiDatePicker: View { - @Environment(\.calendar) var cal - @State var dates: Set = [] - var body: some View { - MultiDatePicker("选择个日子", selection: $dates, in: Date.now...) - Text(s) - } - var s: String { - dates.compactMap { c in - cal.date(from:c)?.formatted(date: .long, time: .omitted) - } - .formatted() - } -} -``` - -## 指定日期范围 - -指定日期的范围,例如只能选择当前日期之后的日期,示例如下: - -```swift -DatePicker( - "选择日期", - selection: $selectedDate, - in: Date()..., - displayedComponents: [.date] -) -.datePickerStyle(WheelDatePickerStyle()) -.labelsHidden() -``` - -在这个示例中: - -- `selection: $selectedDate` 表示选定的日期和时间。 -- `in: Date()...` 表示可选日期的范围。在这个例子中,用户只能选择当前日期之后的日期。你也可以使用 `...Date()` 来限制用户只能选择当前日期之前的日期,或者使用 `Date().addingTimeInterval(86400*7)` 来限制用户只能选择从当前日期开始的接下来一周内的日期。 -- `displayedComponents: [.date]` 表示 `DatePicker` 应该显示哪些组件。在这个例子中,我们只显示日期组件。你也可以使用 `.hourAndMinute` 来显示小时和分钟组件,或者同时显示日期和时间组件。 -- `.datePickerStyle(WheelDatePickerStyle())` 表示 `DatePicker` 的样式。在这个例子中,我们使用滚轮样式。你也可以使用 `GraphicalDatePickerStyle()` 来应用图形样式。 -- `.labelsHidden()` 表示隐藏 `DatePicker` 的标签。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/PhotoPicker(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/PhotoPicker(ap).md" deleted file mode 100644 index 07d1f821e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/PhotoPicker(ap).md" +++ /dev/null @@ -1,131 +0,0 @@ - -## PhotoPicker 使用示例 - -```swift -import SwiftUI -import PhotosUI - -struct ContentView: View { - @State private var selectedItem: PhotosPickerItem? - @State private var selectedPhotoData: Data? - - var body: some View { - NavigationView { - VStack { - if let item = selectedItem, let data = selectedPhotoData, let image = UIImage(data: data) { - Image(uiImage: image) - .resizable() - .scaledToFit() - } else { - Text("选择电影海报") - } - } - .navigationTitle("电影海报") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - PhotosPicker(selection: $selectedItem, matching: .images) { - Label("选择照片", systemImage: "photo") - } - .tint(.indigo) - .controlSize(.extraLarge) - .buttonStyle(.borderedProminent) - } - } - .onChange(of: selectedItem, { oldValue, newValue in - Task { - if let data = try? await newValue?.loadTransferable(type: Data.self) { - selectedPhotoData = data - } - } - }) - } - } -} -``` - -## 限制选择媒体类型 - -我们可以使用 `matching` 参数来过滤 `PhotosPicker` 中显示的媒体类型。这个参数接受一个 `PHAssetMediaType` 枚举值,可以是 `.images`、`.videos`、`.audio`、`.any` 等。 - -例如,如果我们只想显示图片,可以这样设置: - -```swift -PhotosPicker(selection: $selectedItem, matching: .images) { - Label("选择照片", systemImage: "photo") -} -``` - -如果我们想同时显示图片和视频,可以使用 `.any(of:)` 方法: - -```swift -PhotosPicker(selection: $selectedItem, matching: .any(of: [.images, .videos])) { - Label("选择照片", systemImage: "photo") -} -``` - -此外,我们还可以使用 `.not(_:)` 方法来排除某种类型的媒体。例如,如果我们想显示所有的图片,但是不包括 Live Photo,可以这样设置: - -```swift -PhotosPicker(selection: $selectedItem, matching: .any(of: [.images, .not(.livePhotos)])) { - Label("选择照片", systemImage: "photo") -} -``` - -这些设置可以让我们更精确地控制 `PhotosPicker` 中显示的媒体类型。 - - -## 选择多张图片 - -以下示例演示了如何使用 `PhotosPicker` 选择多张图片,并将它们显示在一个 `LazyVGrid` 中: - -```swift -import SwiftUI -import PhotosUI - -struct ContentView: View { - @State private var selectedItems: [PhotosPickerItem] = [PhotosPickerItem]() - @State private var selectedPhotosData: [Data] = [Data]() - - var body: some View { - NavigationStack { - - ScrollView { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) { - ForEach(selectedPhotosData, id: \.self) { photoData in - if let image = UIImage(data: photoData) { - Image(uiImage: image) - .resizable() - .scaledToFit() - .cornerRadius(10.0) - .padding(.horizontal) - } - } - } - } - .navigationTitle("书籍") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - PhotosPicker(selection: $selectedItems, maxSelectionCount: 5, matching: .images) { - Image(systemName: "book.fill") - .foregroundColor(.brown) - } - .onChange(of: selectedItems, { oldValue, newValue in - for newItem in newValue { - Task { - if let data = try? await newItem.loadTransferable(type: Data.self) { - selectedPhotosData.append(data) - } - } - } - }) - } - } - } - } -} -``` - -以上示例中,我们使用了 `PhotosPicker` 的 `maxSelectionCount` 参数来限制用户最多只能选择 5 张图片。当用户选择图片后,我们将图片数据保存在 `selectedPhotosData` 数组中,并在 `LazyVGrid` 中显示这些图片。 - - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/Picker(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/Picker(ap).md" deleted file mode 100644 index 7961d30bc..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/Picker(ap).md" +++ /dev/null @@ -1,109 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/picker-ap01.jpeg) - -SwiftUI 中的 `Picker` 视图是一个用于选择列表中的一个选项的用户界面元素。你可以使用 `Picker` 视图来创建各种类型的选择器,包括滚动选择器、弹出菜单和分段控制。 - -示例代码如下: - -```swift -struct PlayPickerView: View { - @State private var select = 1 - @State private var color = Color.red.opacity(0.3) - - var dateFt: DateFormatter { - let ft = DateFormatter() - ft.dateStyle = .long - return ft - } - @State private var date = Date() - - var body: some View { - - // 默认是下拉的风格 - Form { - Section("选区") { - Picker("选一个", selection: $select) { - Text("1") - .tag(1) - Text("2") - .tag(2) - } - } - } - .padding() - - // Segment 风格, - Picker("选一个", selection: $select) { - Text("one") - .tag(1) - Text("two") - .tag(2) - } - .pickerStyle(SegmentedPickerStyle()) - .padding() - - // 颜色选择器 - ColorPicker("选一个颜色", selection: $color, supportsOpacity: false) - .padding() - - RoundedRectangle(cornerRadius: 8) - .fill(color) - .frame(width: 50, height: 50) - - // 时间选择器 - VStack { - DatePicker(selection: $date, in: ...Date(), displayedComponents: .date) { - Text("选时间") - } - - DatePicker("选时间", selection: $date) - .datePickerStyle(GraphicalDatePickerStyle()) - .frame(maxHeight: 400) - - Text("时间:\(date, formatter: dateFt)") - } - .padding() - } -} -``` - -上面的代码中,有三种类型的 `Picker` 视图: - -1. 默认的下拉风格 `Picker` 视图。这种类型的 `Picker` 视图在 `Form` 中使用,用户可以点击选择器来打开一个下拉菜单,然后从菜单中选择一个选项。 - -```swift -Form { - Section("选区") { - Picker("选一个", selection: $select) { - Text("1") - .tag(1) - Text("2") - .tag(2) - } - } -} -``` - -2. 分段控制风格 `Picker` 视图。这种类型的 `Picker` 视图使用 `SegmentedPickerStyle()` 修饰符,它将选择器显示为一组水平排列的按钮,用户可以点击按钮来选择一个选项。 - -```swift -Picker("选一个", selection: $select) { - Text("one") - .tag(1) - Text("two") - .tag(2) -} -.pickerStyle(SegmentedPickerStyle()) -``` - -3. `ColorPicker` 和 `DatePicker` 视图。这两种类型的视图是 `Picker` 视图的特殊形式,它们分别用于选择颜色和日期。 - -```swift -ColorPicker("选一个颜色", selection: $color, supportsOpacity: false) - -DatePicker("选时间", selection: $date) - .datePickerStyle(GraphicalDatePickerStyle()) -``` - -在所有这些 `Picker` 视图中,你都需要提供一个绑定的选择状态,这个状态会在用户选择一个新的选项时更新。你还需要为每个选项提供一个视图和一个唯一的标签。 - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/WheelPicker(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/WheelPicker(ap).md" deleted file mode 100644 index ffd855642..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/WheelPicker(ap).md" +++ /dev/null @@ -1,62 +0,0 @@ - -本示例是一个可折叠的滚轮选择器 `CollapsibleWheelPicker`。这个选择器允许用户从一组书籍中选择一本。 - -```swift -struct ContentView: View { - @State private var selection = 0 - let items = ["Book 1", "Book 2", "Book 3", "Book 4", "Book 5"] - - var body: some View { - NavigationStack { - Form { - CollapsibleWheelPicker(selection: $selection) { - ForEach(items, id: \.self) { item in - Text("\(item)") - } - } label: { - Text("Books") - Spacer() - Text("\(items[selection])") - } - } - } - } -} - -struct CollapsibleWheelPicker: View where SelectionValue: Hashable, Content: View, Label: View { - @Binding var selection: SelectionValue - @ViewBuilder let content: () -> Content - @ViewBuilder let label: () -> Label - - var body: some View { - CollapsibleView(label: label) { - Picker(selection: $selection, content: content) { - EmptyView() - } - .pickerStyle(.wheel) - } - } -} - -struct CollapsibleView: View where Label: View, Content: View { - @State private var isSecondaryViewVisible = false - - @ViewBuilder let label: () -> Label - @ViewBuilder let content: () -> Content - - var body: some View { - Group { - Button(action: { isSecondaryViewVisible.toggle() }, label: label) - .buttonStyle(.plain) - if isSecondaryViewVisible { - content() - } - } - } -} - -``` - -在 `ContentView` 中,我们创建了一个 `CollapsibleWheelPicker` 视图。这个视图包含一个滚轮样式的选择器,用户可以从中选择一本书。选择的书籍会绑定到 `selection` 变量。 - -`CollapsibleWheelPicker` 视图是一个可折叠的滚轮选择器,它接受一个绑定的选择变量、一个内容视图和一个标签视图。内容视图是一个 `Picker` 视图,用于显示可供选择的书籍。标签视图是一个 `Text` 视图,显示当前选择的书籍。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/\345\255\227\344\275\223Picker(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/\345\255\227\344\275\223Picker(ap).md" deleted file mode 100644 index 91945b8ab..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/\345\255\227\344\275\223Picker(ap).md" +++ /dev/null @@ -1,39 +0,0 @@ - -这段代码实现了一个字体选择器的功能,用户可以在其中选择和查看自己喜欢的字体。 - -```swift -struct ContentView: View { - @State private var fontFamily: String = "" - - var body: some View { - VStack { - Text("选择字体:") - FontPicker(fontFamily: $fontFamily) - .equatable() - } - } -} - -struct FontPicker: View, Equatable { - @Binding var fontFamily: String - - var body: some View { - VStack { - Text("\(fontFamily)") - .font(.custom(fontFamily, size: 20)) - Picker("", selection: $fontFamily) { - ForEach(NSFontManager.shared.availableFontFamilies, id: \.self) { family in - Text(family) - .tag(family) - } - } - Spacer() - } - .padding() - } - - static func == (l: FontPicker, r: FontPicker) -> Bool { - l.fontFamily == r.fontFamily - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/\346\226\207\345\255\227Picker(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/\346\226\207\345\255\227Picker(ap).md" deleted file mode 100644 index 04083b504..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Picker\351\200\211\346\213\251\345\231\250/\346\226\207\345\255\227Picker(ap).md" +++ /dev/null @@ -1,119 +0,0 @@ - -## 基本使用 - -文字 Picker 示例: - -```swift -struct StaticDataPickerView: View { - @State private var selectedCategory = "动作" - - var body: some View { - VStack { - Text("选择的类别: \(selectedCategory)") - - Picker("电影类别", - selection: $selectedCategory) { - Text("动作") - .tag("动作") - Text("喜剧") - .tag("喜剧") - Text("剧情") - .tag("剧情") - Text("恐怖") - .tag("恐怖") - } - } - } -} -``` - -## 使用枚举 - -使用枚举来创建选取器的示例: - -```swift -enum MovieCategory: String, CaseIterable, Identifiable { - case action = "动作" - case comedy = "喜剧" - case drama = "剧情" - case horror = "恐怖" - var id: MovieCategory { self } -} - -struct MoviePicker: View { - @State private var selectedCategory: MovieCategory = .action - - var body: some View { - Picker("电影类别", selection: $selectedCategory) { - ForEach(MovieCategory.allCases) { category in - Text(category.rawValue).tag(category) - } - } - } -} -``` - - -## 样式 - -SwiftUI 提供了多种内置的 `Picker` 样式,以改变 `Picker` 的外观和行为。以下是一些主要的 `Picker` 样式及其使用示例: - -- `DefaultPickerStyle`:根据平台和环境自动调整样式。这是默认的 `Picker` 样式。 - -```swift -Picker("Label", selection: $selection) { - ForEach(0.. - var step: Double - @Binding var isEditing: Bool - - var body: some View { - Slider(value: $value, in: range, step: step) { - Label("亮度", systemImage: "light.max") - } minimumValueLabel: { - Text("\(Int(range.lowerBound))") - } maximumValueLabel: { - Text("\(Int(range.upperBound))") - } onEditingChanged: { - print($0) - } - - } -} -``` - -以上代码中,我们创建了一个 `BrightnessSlider` 控件,它是一个自定义的 `Slider` 控件,用于调整亮度。`BrightnessSlider` 接受一个 `value` 绑定,一个 `range` 范围,一个 `step` 步长,以及一个 `isEditing` 绑定。在 `BrightnessSlider` 中,我们使用 `Slider` 控件来显示亮度调整器。我们还使用 `Label` 来显示亮度调整器的标题,并使用 `minimumValueLabel` 和 `maximumValueLabel` 来显示亮度调整器的最小值和最大值。最后,我们使用 `onEditingChanged` 修饰符来监听亮度调整器的编辑状态。 \ No newline at end of file diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Stepper(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Stepper(ap).md" deleted file mode 100644 index 756fece5e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Stepper(ap).md" +++ /dev/null @@ -1,22 +0,0 @@ - - -`Stepper` 控件允许用户通过点击按钮来增加或减少数值。 - -```swift -struct ContentView: View { - @State private var count: Int = 2 - var body: some View { - Stepper(value: $count, in: 2...20, step: 2) { - Text("共\(count)") - } onEditingChanged: { b in - print(b) - } // end Stepper - } -} -``` - -在 `ContentView` 中,我们定义了一个状态变量 `count`,并将其初始化为 2。然后,我们创建了一个 `Stepper` 视图,并将其绑定到 `count` 状态变量。 - -`Stepper` 视图的值范围为 2 到 20,步进值为 2,这意味着每次点击按钮,`count` 的值会增加或减少 2。我们还添加了一个标签,显示当前的 `count` 值。 - -我们还添加了 `onEditingChanged` 回调,当 `Stepper` 的值改变时,会打印出一个布尔值,表示 `Stepper` 是否正在被编辑。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Toggle(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Toggle(ap).md" deleted file mode 100644 index 4c36ef8ab..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\241\250\345\215\225/Toggle(ap).md" +++ /dev/null @@ -1,209 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/toggle-ap01.png) - - - -## 示例 - -使用示例如下 - -```swift -struct PlayToggleView: View { - @State private var isEnable = false - var body: some View { - // 普通样式 - Toggle(isOn: $isEnable) { - Text("\(isEnable ? "开了" : "关了")") - } - .padding() - - // 按钮样式 - Toggle(isOn: $isEnable) { - Label("\(isEnable ? "打开了" : "关闭了")", systemImage: "cloud.moon") - } - .padding() - .tint(.pink) - .controlSize(.large) - .toggleStyle(.button) - - // Switch 样式 - Toggle(isOn: $isEnable) { - Text("\(isEnable ? "开了" : "关了")") - } - .toggleStyle(SwitchToggleStyle(tint: .orange)) - .padding() - - // 自定义样式 - Toggle(isOn: $isEnable) { - Text(isEnable ? "录音中" : "已静音") - } - .toggleStyle(PCToggleStyle()) - - } -} - -// MARK: - 自定义样式 -struct PCToggleStyle: ToggleStyle { - func makeBody(configuration: Configuration) -> some View { - return HStack { - configuration.label - Image(systemName: configuration.isOn ? "mic.square.fill" : "mic.slash.circle.fill") - .renderingMode(.original) - .resizable() - .frame(width: 30, height: 30) - .onTapGesture { - configuration.isOn.toggle() - } - } - } -} -``` - -## 样式 - -Toggle 可以设置 toggleStyle,可以自定义样式。 - -下表是不同平台支持的样式 - -- DefaultToggleStyle:iOS 表现的是 Switch,macOS 是 Checkbox -- SwitchToggleStyle:iOS 和 macOS 都支持 -- CheckboxToggleStyle:只支持 macOS - -## 纯图像的 Toggle - -```swift -struct ContentView: View { - @State private var isMuted = false - - var body: some View { - Toggle(isOn: $isMuted) { - Image(systemName: isMuted ? "speaker.slash.fill" : "speaker.fill") - .font(.system(size: 50)) - } - .tint(.red) - .toggleStyle(.button) - .clipShape(Circle()) - } -} -``` - -## 自定义 ToggleStyle - -做一个自定义的切换按钮 OfflineModeToggleStyle。这个切换按钮允许用户控制是否开启离线模式。代码如下: - -```swift -struct ContentView: View { - @State private var isOfflineMode = false - - var body: some View { - Toggle(isOn: $isOfflineMode) { - Text("Offline Mode") - } - .toggleStyle(OfflineModeToggleStyle(systemImage: isOfflineMode ? "wifi.slash" : "wifi", activeColor: .blue)) - } -} - -struct OfflineModeToggleStyle: ToggleStyle { - var systemImage: String - var activeColor: Color - - func makeBody(configuration: Configuration) -> some View { - HStack { - configuration.label - - Spacer() - - RoundedRectangle(cornerRadius: 16) - .fill(configuration.isOn ? activeColor : Color(.systemGray5)) - .overlay { - Circle() - .fill(.white) - .padding(2) - .overlay { - Image(systemName: systemImage) - .foregroundColor(configuration.isOn ? activeColor : Color(.systemGray5)) - } - .offset(x: configuration.isOn ? 8 : -8) - } - .frame(width: 50, height: 32) - .onTapGesture { - withAnimation(.spring()) { - configuration.isOn.toggle() - } - } - } - } -} -``` - -以上代码中,我们定义了一个 OfflineModeToggleStyle,它接受两个参数:systemImage 和 activeColor。systemImage 是一个字符串,表示图像的系统名称。activeColor 是一个颜色,表示激活状态的颜色。 - -## 动画化的 Toggle - -以下是一个自定义的切换按钮 MuteToggleStyle。这个切换按钮允许用户控制是否开启静音模式。 - -```swift -struct ContentView: View { - @State private var isMuted = false - - var body: some View { - VStack { - Toggle(isOn: $isMuted) { - Text("Mute Mode") - .foregroundColor(isMuted ? .white : .black) - } - .toggleStyle(MuteToggleStyle()) - .padding() - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} - -struct MuteToggleStyle: ToggleStyle { - var onImage = "speaker.slash.fill" - var offImage = "speaker.2.fill" - - func makeBody(configuration: Configuration) -> some View { - HStack { - configuration.label - - Spacer() - - RoundedRectangle(cornerRadius: 30) - .fill(configuration.isOn ? Color(.systemGray6) : .yellow) - .overlay { - Image(systemName: configuration.isOn ? onImage : offImage) - .resizable() - .scaledToFit() - .clipShape(Circle()) - .padding(5) - .rotationEffect(.degrees(configuration.isOn ? 0 : 180)) - .offset(x: configuration.isOn ? 10 : -10) - } - .frame(width: 50, height: 32) - .onTapGesture { - withAnimation(.easeInOut(duration: 0.2)) { - configuration.isOn.toggle() - } - } - } - } -} - -extension ToggleStyle where Self == MuteToggleStyle { - static var mute: MuteToggleStyle { .init() } -} -``` - -以上代码中,我们定义了一个 MuteToggleStyle,它接受两个参数:onImage 和 offImage。onImage 是一个字符串,表示激活状态的图像的系统名称。offImage 是一个字符串,表示非激活状态的图像的系统名称。 - -## 两个标签的 Toggle - -以下是一个自定义的切换按钮,它有两个标签。这个切换按钮允许用户控制是否开启静音模式。 - -```swift -Toggle(isOn: $mute) { - Text("静音") - Text("这将关闭所有声音") -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Animations\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Animations\345\215\217\350\256\256(ap).md" deleted file mode 100644 index 46067e162..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Animations\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,74 +0,0 @@ - -Animations 协议包括: - -- `Animatable` -- `AnimatableModifier` -- `GeometryEffect` -- `VectorArithmetic` - -动画协议允许我们创建和修改动画。以下是这些协议的介绍和示例: - -- `Animatable`:这个协议定义了一个可以在动画中改变的值。例如,我们可以创建一个表示漫画角色移动的动画: - -```swift -struct ComicCharacterView: View, Animatable { - var position: CGFloat - - var animatableData: CGFloat { - get { position } - set { position = newValue } - } - - var body: some View { - Image("character") - .offset(x: position) - } -} -``` - -在这个例子中,`ComicCharacterView` 是一个自定义的视图,它的 `position` 属性可以在动画中改变。`animatableData` 属性是 `Animatable` 协议的一部分,它表示可以在动画中改变的数据。 - -- `AnimatableModifier`:这个协议定义了一个可以在动画中改变视图的修饰符。例如,我们可以创建一个使漫画角色移动的修饰符: - -```swift -struct MovingModifier: AnimatableModifier { - var position: CGFloat - - var animatableData: CGFloat { - get { position } - set { position = newValue } - } - - func body(content: Content) -> some View { - content.offset(x: position) - } -} -``` - -在这个例子中,`MovingModifier` 是一个自定义的修饰符,它的 `position` 属性可以在动画中改变。我们可以使用这个修饰符来修改我们的 `ComicCharacterView`: - -```swift -ComicCharacterView(position: 100) - .modifier(MovingModifier(position: 200)) -``` - -- `GeometryEffect`:这个协议定义了一个可以改变视图的几何形状的效果。例如,我们可以创建一个使漫画角色旋转的效果: - -```swift -struct RotatingEffect: GeometryEffect { - var angle: Angle - - func effectValue(size: CGSize) -> ProjectionTransform { - ProjectionTransform(CGAffineTransform(rotationAngle: CGFloat(angle.radians))) - } -} -``` - -然后,我们可以使用这个效果来修改我们的 `ComicCharacterView`: - -```swift -ComicCharacterView(position: 100) - .modifier(RotatingEffect(angle: .degrees(45))) -``` - -- `VectorArithmetic`:这个协议定义了一个可以在动画中进行线性插值的类型。例如,`CGFloat` 和 `Double` 都遵循这个协议,我们可以在 `Animatable` 和 `AnimatableModifier` 的动画中使用它们。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Documents\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Documents\345\215\217\350\256\256(ap).md" deleted file mode 100644 index 3db9c14a0..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Documents\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,61 +0,0 @@ - -Documents 协议包括: - -- `FileDocument` -- `ReferenceFileDocument` - -`Documents` 协议主要包括 `FileDocument` 和 `ReferenceFileDocument`,它们用于处理文件的读取和写入。 - -- `FileDocument`:这个协议用于处理文件的读取和写入。它要求我们提供一个 `init(configuration:)` 初始化方法来从文件读取数据,以及一个 `fileWrapper(configuration:)` 方法来将数据写入文件。例如,我们可以创建一个表示漫画的 `FileDocument`: - -```swift -struct ComicDocument: FileDocument { - var comic: String - - init(comic: String) { - self.comic = comic - } - - init(configuration: ReadConfiguration) throws { - guard let data = configuration.file.regularFileContents, - let comic = String(data: data, encoding: .utf8) else { - throw CocoaError(.fileReadCorruptFile) - } - self.comic = comic - } - - func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { - let data = comic.data(using: .utf8)! - return .init(regularFileWithContents: data) - } -} -``` - -在这个例子中,`ComicDocument` 是一个遵循 `FileDocument` 协议的类型,它表示一个漫画。它有一个 `comic` 属性,这个属性是漫画的内容。它的 `init(configuration:)` 方法从文件读取数据,然后将数据转换为字符串。它的 `fileWrapper(configuration:)` 方法将 `comic` 转换为数据,然后将数据写入文件。 - -- `ReferenceFileDocument`:这个协议用于处理文件的读取和写入,但是它不会将文件的内容加载到内存中。它要求我们提供一个 `init(url:)` 初始化方法来从文件读取数据,以及一个 `write(to:)` 方法来将数据写入文件。例如,我们可以创建一个表示漫画的 `ReferenceFileDocument`: - -```swift -struct ComicDocument: ReferenceFileDocument { - var comic: String - - init(comic: String) { - self.comic = comic - } - - init(url: URL, contentType: UTType) throws { - let data = try Data(contentsOf: url) - guard let comic = String(data: data, encoding: .utf8) else { - throw CocoaError(.fileReadCorruptFile) - } - self.comic = comic - } - - func write(to url: URL) throws { - let data = comic.data(using: .utf8)! - try data.write(to: url) - } -} -``` - -在这个例子中,`ComicDocument` 是一个遵循 `ReferenceFileDocument` 协议的类型,它表示一个漫画。它的 `init(url:)` 方法从文件读取数据,然后将数据转换为字符串。它的 `write(to:)` 方法将 `comic` 转换为数据,然后将数据写入文件。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Legacy bridges\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Legacy bridges\345\215\217\350\256\256(ap).md" deleted file mode 100644 index 85cd2c76e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Legacy bridges\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,62 +0,0 @@ - -Legacy bridges 协议包括: - -- `UIViewControllerRepresentable` (iOS) -- `UIViewRepresentable` (iOS) -- `NSViewControllerRepresentable` (macOS) -- `NSViewRepresentable` (macOS) -- `WKInterfaceObjectRepresentable` (watchOS) - -Legacy bridges 协议允许我们在 SwiftUI 视图中使用 UIKit(iOS)、AppKit(macOS)或 WatchKit(watchOS)的视图和视图控制器。这些协议包括: - -- `UIViewControllerRepresentable`:这个协议允许我们在 SwiftUI 视图中使用 UIKit 的视图控制器。例如,我们可以创建一个表示漫画的 `UIViewController`,然后在 SwiftUI 视图中显示它: - -```swift -struct ComicViewController: UIViewControllerRepresentable { - func makeUIViewController(context: Context) -> UIViewController { - let viewController = UIViewController() - viewController.view.backgroundColor = .red - return viewController - } - - func updateUIViewController(_ uiViewController: UIViewController, context: Context) { - // 更新视图控制器 - } -} -``` - -- `UIViewRepresentable`:这个协议允许我们在 SwiftUI 视图中使用 UIKit 的视图。例如,我们可以创建一个表示漫画的 `UIView`,然后在 SwiftUI 视图中显示它: - -```swift -struct ComicView: UIViewRepresentable { - func makeUIView(context: Context) -> UIView { - let view = UIView() - view.backgroundColor = .red - return view - } - - func updateUIView(_ uiView: UIView, context: Context) { - // 更新视图 - } -} -``` - -- `NSViewControllerRepresentable` 和 `NSViewRepresentable`:这些协议允许我们在 SwiftUI 视图中使用 AppKit 的视图和视图控制器。它们的使用方式与 `UIViewControllerRepresentable` 和 `UIViewRepresentable` 类似,但是它们是用于 macOS 的。 - -- `WKInterfaceObjectRepresentable`:这个协议允许我们在 SwiftUI 视图中使用 WatchKit 的界面对象。例如,我们可以创建一个表示漫画的 `WKInterfaceLabel`,然后在 SwiftUI 视图中显示它: - -```swift -struct ComicLabel: WKInterfaceObjectRepresentable { - func makeWKInterfaceObject(context: Context) -> WKInterfaceLabel { - let label = WKInterfaceLabel() - label.setText("漫画") - return label - } - - func updateWKInterfaceObject(_ wkInterfaceObject: WKInterfaceLabel, context: Context) { - // 更新界面对象 - } -} -``` - -在这些例子中,我们创建了表示漫画的视图和视图控制器,然后使用 Legacy bridges 协议在 SwiftUI 视图中显示它们。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Previews\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Previews\345\215\217\350\256\256(ap).md" deleted file mode 100644 index 0d5f5c1aa..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Previews\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,43 +0,0 @@ - -Previews 协议包括: - -- `PreviewContext` -- `PreviewContextKey` -- `PreviewProvider` - -`PreviewProvider` 协议允许我们为视图提供预览。这些预览可以在 Xcode 的预览窗口中显示,帮助我们在编写代码时看到视图的外观。 - -例如,我们可以创建一个表示漫画角色的视图,然后为它提供一个预览: - -```swift -struct ComicCharacterView: View { - var body: some View { - Text("漫画角色") - } -} - -struct ComicCharacterView_Previews: PreviewProvider { - static var previews: some View { - ComicCharacterView() - } -} -``` - -在这个例子中,`ComicCharacterView` 是一个自定义的视图,它显示一条表示漫画角色的文本。`ComicCharacterView_Previews` 是一个遵循 `PreviewProvider` 协议的类型,它提供了 `ComicCharacterView` 的预览。 - -`PreviewContext` 和 `PreviewContextKey` 是用于自定义预览环境的类型。例如,我们可以创建一个自定义的 `PreviewContextKey`,然后使用 `PreviewContext` 来设置它的值: - -```swift -struct ComicStyleKey: PreviewContextKey { - static let defaultValue: String = "普通" -} - -struct ComicCharacterView_Previews: PreviewProvider { - static var previews: some View { - ComicCharacterView() - .previewContext(PreviewContext(ComicStyleKey.self, "暗黑")) - } -} -``` - -在这个例子中,`ComicStyleKey` 是一个自定义的 `PreviewContextKey`,它表示漫画的样式。我们在 `ComicCharacterView_Previews` 中使用 `previewContext()` 修饰符来设置它的值为 "暗黑"。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Responder chain\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Responder chain\345\215\217\350\256\256(ap).md" deleted file mode 100644 index 033bf18a1..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Responder chain\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,59 +0,0 @@ - -Responder chain 协议包括: - -- `Commands` -- `FocusedValueKey` - -`Responder chain` 是一个事件处理机制,它允许事件从当前视图传递到视图层次结构中的其他视图。这个机制主要涉及到两个协议:`Commands` 和 `FocusedValueKey`。 - -`Commands` 协议允许我们定义一组命令,这些命令可以在菜单中显示,并且可以响应用户的操作。例如,我们可以创建一个命令,它允许用户切换漫画的显示模式: - -```swift -struct ComicCommands: Commands { - var body: some Commands { - CommandMenu("漫画") { - Button("切换模式") { - // 切换漫画的显示模式 - } - } - } -} - -@main -struct MyApp: App { - var body: some Scene { - WindowGroup { - ContentView() - .commands { - ComicCommands() - } - } - } -} -``` - -在这个例子中,`ComicCommands` 是一个遵循 `Commands` 协议的类型,它定义了一个命令菜单,菜单中有一个按钮,点击这个按钮可以切换漫画的显示模式。我们在 `MyApp` 中使用 `.commands` 修饰符来添加这个命令菜单。 - -`FocusedValueKey` 协议允许我们定义一个键,这个键可以用于获取或设置焦点视图的值。例如,我们可以创建一个键,它表示当前漫画的标题: - -```swift -struct ComicTitleKey: FocusedValueKey { - typealias Value = String -} - -extension FocusedValues { - var comicTitle: ComicTitleKey.Value? { - get { self[ComicTitleKey.self] } - set { self[ComicTitleKey.self] = newValue } - } -} -``` - -然后,我们可以在视图中使用 `.focusedValue()` 修饰符来设置这个键的值: - -```swift -Text("漫画标题") - .focusedValue(ComicTitleKey.self, "漫画标题") -``` - -在这个例子中,`ComicTitleKey` 是一个遵循 `FocusedValueKey` 协议的类型,它表示当前漫画的标题。我们在 `Text` 视图中使用 `.focusedValue()` 修饰符来设置这个键的值为 "漫画标题"。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Shapes\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Shapes\345\215\217\350\256\256(ap).md" deleted file mode 100644 index 4a724271a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Shapes\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,43 +0,0 @@ - -Shapes 协议包括: - -- `Shape` -- `InsettableShape` - -`Shape` 和 `InsettableShape` 是用于创建和修改形状的协议。 - -- `Shape`:这个协议定义了一个可以在画布上绘制的形状。例如,我们可以创建一个表示漫画气泡的形状: - -```swift -struct ComicBubble: Shape { - func path(in rect: CGRect) -> Path { - var path = Path() - path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2, startAngle: .zero, endAngle: .degrees(360), clockwise: true) - return path - } -} -``` - -在这个例子中,`ComicBubble` 是一个自定义的形状,它在给定的矩形中绘制一个圆形。 - -- `InsettableShape`:这个协议定义了一个可以插入或扩展其边界的形状。例如,我们可以创建一个可以插入的漫画气泡形状: - -```swift -struct InsettableComicBubble: InsettableShape { - var insetAmount: CGFloat = 0 - - func path(in rect: CGRect) -> Path { - var path = Path() - path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2 - insetAmount, startAngle: .zero, endAngle: .degrees(360), clockwise: true) - return path - } - - func inset(by amount: CGFloat) -> some InsettableShape { - var bubble = self - bubble.insetAmount += amount - return bubble - } -} -``` - -在这个例子中,`InsettableComicBubble` 是一个自定义的可以插入的形状,它在给定的矩形中绘制一个圆形,但是半径减去了插入量。`inset(by:)` 方法返回一个新的形状,其插入量增加了指定的量。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Style\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Style\345\215\217\350\256\256(ap).md" deleted file mode 100644 index c81e56154..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Style\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,55 +0,0 @@ - -样式风格协议包括: - -- `ButtonStyle` -- `DatePickerStyle` (iOS, macOS)  `DatePickerStyle` (iOS、macOS) -- `GaugeStyle` (watchOS)  `GaugeStyle` (watchOS操作系统) -- `GroupBoxStyle` (iOS, macOS)  `GroupBoxStyle` (iOS、macOS) -- `IndexViewStyle` (iOS, tvOS)  `IndexViewStyle` (iOS、tvOS) -- `LabelStyle` -- `ListStyle` -- `MenuButtonStyle` (macOS)  `MenuButtonStyle` (macOS操作系统) -- `MenuStyle` (iOS, macOS)  `MenuStyle` (iOS、macOS) -- `NavigationViewStyle` -- `PickerStyle` -- `PrimitiveButtonStyle` -- `ProgressViewStyle` -- `ShapeStyle` -- `TabViewStyle` -- `TextFieldStyle` -- `ToggleStyle` -- `WindowStyle` (macOS)  `WindowStyle` (macOS操作系统) -- `WindowToolbarStyle` (macOS)  `WindowToolbarStyle` (macOS操作系统) - -在 SwiftUI 中,样式风格协议允许我们自定义各种视图的外观和行为。这些协议包括 `ButtonStyle`,`DatePickerStyle`,`ListStyle`,`PickerStyle`,`PrimitiveButtonStyle`,`ProgressViewStyle`,`ShapeStyle`,`TabViewStyle`,`TextFieldStyle`,`ToggleStyle` 等。 - -例如,`ButtonStyle` 协议允许我们自定义按钮的外观和行为。我们可以创建一个自定义的漫画风格按钮样式: - -```swift -struct ComicButtonStyle: ButtonStyle { - func makeBody(configuration: Configuration) -> some View { - configuration.label - .font(.title) - .padding() - .background(Color.yellow) - .cornerRadius(10) - .shadow(color: .black, radius: 10, x: 0, y: 0) - .scaleEffect(configuration.isPressed ? 0.9 : 1.0) - } -} -``` - -在这个例子中,`ComicButtonStyle` 是一个自定义的漫画风格按钮样式。按钮的字体是标题字体,四周有一定的填充,背景色是黄色,角落是圆形的,有一个黑色的阴影,当按钮被按下时,会缩小到原来的 90%。 - -你可以像这样使用 `ComicButtonStyle`: - -```swift -Button(action: { - print("漫画按钮被点击!") -}) { - Text("点击我") -} -.buttonStyle(ComicButtonStyle()) -``` - -在这个例子中,按钮的动作是打印一条消息,按钮的标签是一个文本视图。我们使用 `.buttonStyle()` 修饰符来应用 `ComicButtonStyle`。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Toolbar\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Toolbar\345\215\217\350\256\256(ap).md" deleted file mode 100644 index 5c4f7d7d7..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/Toolbar\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,57 +0,0 @@ - -Toolbar 协议包括: - -- `ToolbarContent` -- `CustomizableToolbarContent` - -在 SwiftUI 中,`Toolbar` 是一个用于在视图中添加工具栏的修饰符。这个修饰符可以接受一个遵循 `ToolbarContent` 协议的类型,这个类型定义了工具栏的内容。 - -例如,我们可以创建一个视图,它有一个工具栏,工具栏中有一个按钮,点击这个按钮可以切换漫画的显示模式: - -```swift -struct ComicView: View { - @State private var isDarkMode = false - - var body: some View { - Text(isDarkMode ? "暗黑模式的漫画" : "普通模式的漫画") - .toolbar { - ToolbarItem { - Button("切换模式") { - isDarkMode.toggle() - } - } - } - } -} -``` - -在这个例子中,`ComicView` 是一个自定义的视图,它有一个 `@State` 属性 `isDarkMode`,这个属性表示是否是暗黑模式。视图的主体是一个文本,它根据 `isDarkMode` 的值来显示不同的漫画。视图还有一个工具栏,工具栏中有一个按钮,点击这个按钮可以切换 `isDarkMode` 的值。 - -`CustomizableToolbarContent` 是一个协议,它允许我们定义一个可以自定义的工具栏内容。例如,我们可以创建一个遵循 `CustomizableToolbarContent` 协议的类型,然后在视图中使用它: - -```swift -struct ComicToolbar: CustomizableToolbarContent { - var body: some ToolbarContent { - ToolbarItem { - Button("切换模式") { - // 切换漫画的显示模式 - } - } - } - - var placement: ToolbarItemPlacement { - .navigationBarTrailing - } -} - -struct ComicView: View { - var body: some View { - Text("漫画") - .toolbar { - ComicToolbar() - } - } -} -``` - -在这个例子中,`ComicToolbar` 是一个遵循 `CustomizableToolbarContent` 协议的类型,它定义了一个工具栏项,这个工具栏项是一个按钮,点击这个按钮可以切换漫画的显示模式。`ComicToolbar` 还定义了工具栏项的位置,它是在导航栏的尾部。我们在 `ComicView` 中使用 `.toolbar` 修饰符来添加这个工具栏。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\345\260\217\347\273\204\344\273\266\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\345\260\217\347\273\204\344\273\266\345\215\217\350\256\256(ap).md" deleted file mode 100644 index b925440a8..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\345\260\217\347\273\204\344\273\266\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,42 +0,0 @@ - - -小组件包括 - -- `Widget` -- `WidgetBundle` -- `WidgetConfiguration` - -在 iOS 和 macOS 中,小组件协议允许我们创建和配置小部件,这些小部件可以在主屏幕或者通知中心显示应用程序的信息。以下是这些协议的介绍和示例: - -- `Widget`:这个协议定义了一个小部件的基本接口。例如,我们可以创建一个显示漫画书标题的小部件: - -```swift -struct ComicWidget: Widget { - private let kind: String = "ComicWidget" - - public var body: some WidgetConfiguration { - StaticConfiguration(kind: kind, provider: Provider()) { entry in - ComicWidgetEntryView(entry: entry) - } - .configurationDisplayName("Comic Widget") - .description("This is a comic widget.") - } -} -``` - -- `WidgetBundle`:这个协议允许我们将多个小部件组合在一起。例如,我们可以创建一个包含多个漫画小部件的小部件包: - -```swift -@main -struct ComicWidgetBundle: WidgetBundle { - @WidgetBundleBuilder - var body: some Widget { - ComicWidget() - AnotherComicWidget() - } -} -``` - -- `WidgetConfiguration`:这个协议定义了一个小部件的配置。在上面的 `ComicWidget` 示例中,我们使用了 `StaticConfiguration`,它是 `WidgetConfiguration` 的一个具体实现,用于创建静态小部件。 - -注意:在这些示例中,`Provider` 和 `ComicWidgetEntryView` 需要你自己定义。`Provider` 是一个遵循 `TimelineProvider` 协议的类型,它负责提供小部件的时间线数据。`ComicWidgetEntryView` 是一个视图,用于显示小部件的内容。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\347\211\271\345\256\232\346\203\205\345\206\265\350\247\206\345\233\276\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\347\211\271\345\256\232\346\203\205\345\206\265\350\247\206\345\233\276\345\215\217\350\256\256(ap).md" deleted file mode 100644 index 752320eba..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\347\211\271\345\256\232\346\203\205\345\206\265\350\247\206\345\233\276\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,101 +0,0 @@ - -特定情况下使用的视图协议包括: - -- `AlignmentID` 自定义对齐方式 -- `PreferenceKey` 读取子值 -- `DropDelegate` -- `DynamicProperty` -- `DynamicViewContent` 数据收集视图 -- `Gesture` - -我们将介绍其中的几个示例。 - -- `AlignmentID`:这个协议允许我们定义自定义的对齐方式。例如,我们可以创建一个对齐方式,它将视图对齐到漫画的底部: - -```swift -struct ComicBottomAlignment: AlignmentID { - static func defaultValue(in context: ViewDimensions) -> CGFloat { - return context[.bottom] - } -} - -let comicBottomAlignment = Alignment(horizontal: .center, vertical: ComicBottomAlignment.self) -``` - -- `PreferenceKey`:这个协议允许我们从子视图读取值。例如,我们可以创建一个偏好键,它读取漫画的标题: - -```swift -struct ComicTitleKey: PreferenceKey { - static var defaultValue: String = "" - static func reduce(value: inout String, nextValue: () -> String) { - value = nextValue() - } -} - -Text("漫画标题") - .preference(key: ComicTitleKey.self, value: "漫画标题") -``` - -- `DropDelegate`:这个协议允许我们处理拖放操作。例如,我们可以创建一个拖放代理,它允许用户将漫画拖放到视图中: - -```swift -struct ComicDropDelegate: DropDelegate { - func performDrop(info: DropInfo) -> Bool { - // 处理漫画的拖放 - return true - } -} - -VStack { - Text("拖放漫画到这里") -} -.onDrop(of: [.fileURL], delegate: ComicDropDelegate()) -``` - -- `DynamicProperty`:这个协议允许我们创建动态的属性。例如,我们可以创建一个动态的属性,它表示漫画的标题: - -```swift -struct ComicTitle: DynamicProperty { - @State private var title = "漫画标题" - - var body: some View { - Text(title) - } -} -``` - -- `DynamicViewContent`:这个协议允许我们创建动态的视图内容。例如,我们可以创建一个动态的视图内容,它表示一个漫画列表: - -```swift -struct ComicList: View, DynamicViewContent { - var comics: [String] - - var body: some View { - List(comics, id: \.self) { comic in - Text(comic) - } - } -} -``` - -- `Gesture`:这个协议允许我们创建手势。例如,我们可以创建一个手势,它允许用户通过滑动来切换漫画: - -```swift -struct ComicView: View { - @State private var index = 0 - var comics: [String] - - var body: some View { - Text(comics[index]) - .gesture(DragGesture().onEnded { value in - if value.translation.width < 0 { - // 向左滑动,显示下一部漫画 - index = (index + 1) % comics.count - } else { - // 向右滑动,显示上一部漫画 - index = (index - 1 + comics.count) % comics.count - } - }) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-Environment(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-Environment(ap).md" deleted file mode 100644 index 89ad22722..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-Environment(ap).md" +++ /dev/null @@ -1,48 +0,0 @@ - -Environment 协议包括: - -- `EnvironmentKey` -- `EnvironmentalModifier` - -在 SwiftUI 中,`Environment` 是一个属性包装器,它允许我们从视图层次结构的环境中获取值。这些值可以是系统设置,如用户界面样式,或者是我们自定义的环境值。 - -例如,我们可以创建一个视图,它使用 `@Environment` 属性包装器来获取当前的用户界面样式,并根据这个样式来显示不同的漫画: - -```swift -struct ComicView: View { - @Environment(\.colorScheme) var colorScheme - - var body: some View { - if colorScheme == .dark { - Text("显示暗黑模式的漫画") - } else { - Text("显示普通模式的漫画") - } - } -} -``` - -在这个例子中,`ComicView` 是一个自定义的视图,它使用 `@Environment` 属性包装器来获取当前的用户界面样式。如果样式是暗黑模式,它显示一条表示暗黑模式的漫画的文本;否则,它显示一条表示普通模式的漫画的文本。 - -注意:`\.colorScheme` 是一个 `EnvironmentKey`,它表示用户界面样式的环境值。我们可以使用 `@Environment` 属性包装器来获取这个值。 - -另外,`EnvironmentalModifier` 是一个协议,它定义了一个可以修改环境值的修饰符。例如,我们可以创建一个修饰符,它将用户界面样式设置为暗黑模式: - -```swift -struct DarkModeModifier: ViewModifier { - @Environment(\.colorScheme) var colorScheme - - func body(content: Content) -> some View { - content.environment(\.colorScheme, .dark) - } -} -``` - -然后,我们可以使用这个修饰符来修改我们的 `ComicView`: - -```swift -ComicView() - .modifier(DarkModeModifier()) -``` - -在这个例子中,`DarkModeModifier` 是一个自定义的修饰符,它使用 `@Environment` 属性包装器来获取当前的用户界面样式,然后使用 `.environment()` 修饰符将样式设置为暗黑模式。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-\346\240\270\345\277\203\345\215\217\350\256\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-\346\240\270\345\277\203\345\215\217\350\256\256(ap).md" deleted file mode 100644 index b589255ce..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-\346\240\270\345\277\203\345\215\217\350\256\256(ap).md" +++ /dev/null @@ -1,60 +0,0 @@ - -核心协议包括: - -- `View` -- `ViewModifier` -- `App` -- `Scene` - -以下是 SwiftUI 的核心协议的介绍和示例: - -- `View`:这是所有 SwiftUI 视图的基础协议。它定义了一个视图的基本接口,包括其主体和一些关联类型。例如,我们可以创建一个表示漫画书的视图: - -```swift -struct ComicBookView: View { - var title: String - - var body: some View { - Text(title) - .font(.title) - .padding() - } -} -``` - -- `ViewModifier`:这个协议定义了可以修改视图并返回新视图的类型。例如,我们可以创建一个修改器,使文本看起来像漫画对话框: - -```swift -struct ComicSpeechModifier: ViewModifier { - func body(content: Content) -> some View { - content - .font(.title) - .padding() - .background(Color.yellow) - .cornerRadius(10) - } -} -``` - -然后,我们可以使用这个修改器来修改我们的 `ComicBookView`: - -```swift -ComicBookView(title: "Hello, Comic World!") - .modifier(ComicSpeechModifier()) -``` - -- `App`:这个协议定义了一个应用程序的结构。它包括一个场景的主体,这个场景是应用程序的入口点。例如,我们可以创建一个漫画应用程序: - -```swift -@main -struct ComicApp: App { - var body: some Scene { - WindowGroup { - ComicBookView(title: "Hello, Comic World!") - .modifier(ComicSpeechModifier()) - } - } -} -``` - -- `Scene`:这个协议定义了应用程序的一个场景,例如一个窗口或者一个文档。在上面的 `ComicApp` 示例中,我们使用了 `WindowGroup`,它是 `Scene` 的一个具体实现,用于管理应用程序的主窗口。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-\347\256\200\344\273\213(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-\347\256\200\344\273\213(ap).md" deleted file mode 100644 index d503b8b23..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\345\215\217\350\256\256/\350\247\206\345\233\276\345\215\217\350\256\256-\347\256\200\344\273\213(ap).md" +++ /dev/null @@ -1,28 +0,0 @@ - - -SwiftUI 的视图协议定义了构建用户界面的基本规则和结构。以下是一些主要的视图协议: - -- `View`:这是所有 SwiftUI 视图的基础协议。它定义了一个视图的基本接口,包括其主体和一些关联类型。 -- `ViewModifier`:这个协议定义了可以修改视图并返回新视图的类型。 -- `App`:这个协议定义了一个应用程序的结构。它包括一个场景的主体,这个场景是应用程序的入口点。 -- `Scene`:这个协议定义了应用程序的一个场景,例如一个窗口或者一个文档。 - -此外,SwiftUI 还提供了一系列的样式协议,如 `ButtonStyle`,`DatePickerStyle`,`LabelStyle` 等,这些协议允许我们自定义视图的外观和行为。 - -还有一些专门用于构建和配置小部件的协议,如 `Widget`,`WidgetBundle` 和 `WidgetConfiguration`。 - -SwiftUI 还提供了一些用于定义形状的协议,如 `Shape` 和 `InsettableShape`,以及用于定义动画的协议,如 `Animatable` 和 `AnimatableModifier`。 - -环境协议允许我们在视图层次结构中传递数据,如 `EnvironmentKey` 和 `EnvironmentalModifier`。 - -预览协议允许我们为预览提供正确的上下文,如 `PreviewContext` 和 `PreviewProvider`。 - -传统桥梁协议允许我们访问传统视图定义,如 `UIViewControllerRepresentable` 和 `UIViewRepresentable`。 - -响应者链协议允许我们处理来自应用程序不同部分的事件,如 `Commands` 和 `FocusedValueKey`。 - -工具栏协议允许我们优化/组织工具栏,如 `ToolbarContent` 和 `CustomizableToolbarContent`。 - -文档协议用于定义支持的文件类型,如 `FileDocument` 和 `ReferenceFileDocument`。 - -最后,SwiftUI 还提供了一些一次性协议,如 `AlignmentID`,`PreferenceKey`,`DropDelegate`,`DynamicProperty`,`DynamicViewContent` 和 `Gesture`。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Button(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Button(ap).md" deleted file mode 100644 index 049f3b23f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Button(ap).md" +++ /dev/null @@ -1,280 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/button-ap01.png) - -```swift -struct PlayButtonView: View { - var asyncAction: () async -> Void = { - do { - try await Task.sleep(nanoseconds: 300_000_000) - } catch {} - } - @State private var isFollowed: Bool = false - var body: some View { - VStack { - // 常用方式 - Button { - print("Clicked") - } label: { - Image(systemName: "ladybug.fill") - Text("Report Bug") - } - - // 图标 - Button(systemIconName: "ladybug.fill") { - print("bug") - } - .buttonStyle(.plain) // 无背景 - .simultaneousGesture(LongPressGesture().onEnded({ _ in - print("长按") // macOS 暂不支持 - })) - .simultaneousGesture(TapGesture().onEnded({ _ in - print("短按") // macOS 暂不支持 - })) - - - // iOS 15 修改器的使用。role 在 macOS 上暂不支持 - Button("要删除了", role: .destructive) { - print("删除") - } - .tint(.purple) - .controlSize(.large) // .regular 是默认大小 - .buttonStyle(.borderedProminent) // borderedProminent 可显示 tint 的设置。还有 bordered、plain 和 borderless 可选。 - .clipShape(RoundedRectangle(cornerRadius: 5)) - .accentColor(.pink) - .buttonBorderShape(.automatic) // 会依据 controlSize 调整边框样式 - .background(.ultraThinMaterial, in: Capsule()) // 添加材质就像在视图和背景间加了个透明层达到模糊的效果。效果由高到底分别是.ultraThinMaterial、.thinMaterial、.regularMaterial、.thickMaterial、.ultraThickMaterial。 - - // 风格化 - Button(action: { - // - }, label: { - Text("风格化").font(.largeTitle) - }) - .buttonStyle(PStarmingButtonStyle()) - - - // 自定义 Button - PCustomButton("点一下触发") { - print("Clicked!") - } - - // 自定义 ButtonStyle - Button { - print("Double Clicked!") - } label: { - Text("点两下触发") - } - .buttonStyle(PCustomPrimitiveButtonStyle()) - - // 将 Text 视图加上另一个 Text 视图中,类型仍还是 Text。 - PCustomButton(Text("点我 ").underline() + Text("别犹豫").font(.title) + Text("🤫悄悄说声,有惊喜").font(.footnote).foregroundColor(.secondary)) { - print("多 Text 组合标题按钮点击!") - } - - // 异步按钮 - ButtonAsync { - await asyncAction() - isFollowed = true - } label: { - if isFollowed == true { - Text("已关注") - } else { - Text("关注") - } - } - .font(.largeTitle) - .disabled(isFollowed) - .buttonStyle(PCustomButtonStyle(backgroundColor: isFollowed == true ? .gray : .pink)) - } - .padding() - .background(Color.skeumorphismBG) - - } -} - -// MARK: - 异步操作的按钮 -struct ButtonAsync: View { - var doAsync: () async -> Void - @ViewBuilder var label: () -> Label - @State private var isRunning = false // 避免连续点击造成重复执行事件 - - var body: some View { - Button { - isRunning = true - Task { - await doAsync() - isRunning = false - } - } label: { - label().opacity(isRunning == true ? 0 : 1) - if isRunning == true { - ProgressView() - } - } - .disabled(isRunning) - - } -} - -// MARK: - 扩展 Button -// 使用 SFSymbol 做图标 -extension Button where Label == Image { - init(systemIconName: String, done: @escaping () -> Void) { - self.init(action: done) { - Image(systemName: systemIconName) - .renderingMode(.original) - } - } -} - -// MARK: - 自定义 Button -struct PCustomButton: View { - let desTextView: Text - let act: () -> Void - - init(_ des: LocalizedStringKey, act: @escaping () -> Void) { - self.desTextView = Text(des) - self.act = act - } - - var body: some View { - Button { - act() - } label: { - desTextView.bold() - } - .buttonStyle(.starming) - } -} - -extension PCustomButton { - init(_ desTextView: Text, act: @escaping () -> Void) { - self.desTextView = desTextView - self.act = act - } -} - -// 点语法使用自定义样式 -extension ButtonStyle where Self == PCustomButtonStyle { - static var starming: PCustomButtonStyle { - PCustomButtonStyle(cornerRadius: 15) - } -} - - -// MARK: - ButtonStyle -struct PCustomButtonStyle: ButtonStyle { - var cornerRadius:Double = 10 - var backgroundColor: Color = .pink - func makeBody(configuration: Configuration) -> some View { - HStack { - Spacer() - configuration.label - Spacer() - } - .padding() - .background( - RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) - .fill(backgroundColor) - .shadow(color: configuration.isPressed ? .white : .black, radius: 1, x: 0, y: 1) - ) - .opacity(configuration.isPressed ? 0.5 : 1) - .scaleEffect(configuration.isPressed ? 0.99 : 1) - - } -} - -// MARK: - PrimitiveButtonStyle -struct PCustomPrimitiveButtonStyle: PrimitiveButtonStyle { - func makeBody(configuration: Configuration) -> some View { - // 双击触发 - configuration.label - .onTapGesture(count: 2) { - configuration.trigger() - } - // 手势识别 - Button(configuration) - .gesture( - LongPressGesture() - .onEnded({ _ in - configuration.trigger() - }) - ) - } -} - -// MARK: - 风格化 -struct PStarmingButtonStyle: ButtonStyle { - var backgroundColor = Color.skeumorphismBG - func makeBody(configuration: Configuration) -> some View { - HStack { - Spacer() - configuration.label - Spacer() - } - .padding(20) - .background( - ZStack { - RoundedRectangle(cornerRadius: 10, style: .continuous) - .shadow(color: .white, radius: configuration.isPressed ? 7 : 10, x: configuration.isPressed ? -5 : -10, y: configuration.isPressed ? -5 : -10) - .shadow(color: .black, radius: configuration.isPressed ? 7 : 10, x: configuration.isPressed ? 5 : 10, y: configuration.isPressed ? 5 : 10) - .blendMode(.overlay) - RoundedRectangle(cornerRadius: 10, style: .continuous) - .fill(backgroundColor) - } - ) - .scaleEffect(configuration.isPressed ? 0.98 : 1) - } -} - -extension Color { - static let skeumorphismBG = Color(hex: "f0f0f3") -} - -extension Color { - init(hex: String) { - var rgbValue: UInt64 = 0 - Scanner(string: hex).scanHexInt64(&rgbValue) - - let r = (rgbValue & 0xff0000) >> 16 - let g = (rgbValue & 0xff00) >> 8 - let b = rgbValue & 0xff - - self.init(red: Double(r) / 0xff, green: Double(g) / 0xff, blue: Double(b) / 0xff) - } -} -``` - -`.buttonStyle` 可组合,示例如下: -```swift -struct PButtonStyleComposition: View { - @State private var isT = false - var body: some View { - Section("标签") { - VStack(alignment: .leading) { - HStack { - Toggle("Swift", isOn: $isT) - Toggle("SwiftUI", isOn: $isT) - } - HStack { - Toggle("Swift Chart", isOn: $isT) - Toggle("Navigation API", isOn: $isT) - } - } - .toggleStyle(.button) - .buttonStyle(.bordered) - } - } -} -``` - -Tap Location 可以获取点击的位置,示例代码如下: -```swift -Rectangle() - .fill(.green) - .frame(width: 50, height: 50) - .onTapGesture(coordinateSpace: .global) { location in - print("Tap in \(location)") - } -``` - -其中 coordinateSpace 指定为 `.global` 表示位置是相对屏幕左上角,默认是相对当前视图的左上角的位置。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Keyboard(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Keyboard(ap).md" deleted file mode 100644 index 903ceebf0..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Keyboard(ap).md" +++ /dev/null @@ -1,27 +0,0 @@ -键盘快捷键的使用方法如下: - -```swift -struct PlayKeyboard: View { - var body: some View { - Button(systemIconName: "camera.shutter.button") { - print("按了回车键") - } - .keyboardShortcut(.defaultAction) // 回车 - - Button("ESC", action: { - print("按了 ESC") - }) - .keyboardShortcut(.cancelAction) // ESC 键 - - Button("CMD + p") { - print("按了 CMD + p") - } - .keyboardShortcut("p") - - Button("SHIFT + p") { - print("按了 SHIFT + p") - } - .keyboardShortcut("p", modifiers: [.shift]) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/ShareLink(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/ShareLink(ap).md" deleted file mode 100644 index 306176a05..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/ShareLink(ap).md" +++ /dev/null @@ -1,14 +0,0 @@ -ShareLink 视图可以让你轻松共享数据。示例代码如下: -```swift -struct PShareLink: View { - let url = URL(string: "https://ming1016.github.io/")! - var body: some View { - ShareLink(item: url, message: Text("戴铭的博客")) - ShareLink("戴铭的博客", item: url) - ShareLink(item: url) { - Label("戴铭的博客", systemImage: "swift") - } - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Transferable(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Transferable(ap).md" deleted file mode 100644 index 4da81e965..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/Transferable(ap).md" +++ /dev/null @@ -1,25 +0,0 @@ -Transferable 协议使数据可以用于剪切板、拖放和 Share Sheet。 - -可以在自己应用程序之间或你的应用和其他应用之间发送或接受可传输项目。 - -支持 SwiftUI 来使用。 - -官方文档 [Core Transferable](https://developer.apple.com/documentation/CoreTransferable) - -session [Meet Transferable](https://developer.apple.com/videos/play/wwdc2022-10062) - -新增一个专门用来接受 Transferable 的按钮视图 PasteButton,使用示例如下: -```swift -struct PPasteButton: View { - @State private var s = "戴铭" - var body: some View { - TextField("输入", text: $s) - .textFieldStyle(.roundedBorder) - PasteButton(payloadType: String.self) { str in - guard let first = str.first else { return } - s = first - } - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/\350\277\233\345\272\246(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/\350\277\233\345\272\246(ap).md" deleted file mode 100644 index 462a79a21..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\345\233\276\347\273\204\344\273\266/\350\277\233\345\272\246(ap).md" +++ /dev/null @@ -1,131 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/progress-ap01.jpeg) - -用 ProgressViewStyle 协议,可以创建自定义的进度条视图。在 WatchOS 上会多一个 Guage 视图。 - -```swift -struct PlayProgressView: View { - @State private var v: CGFloat = 0.0 - var body: some View { - VStack { - // 默认旋转 - ProgressView() - - // 有进度条 - ProgressView(value: v / 100) - .tint(.yellow) - - ProgressView(value: v / 100) { - Image(systemName: "music.note.tv") - } - .progressViewStyle(CircularProgressViewStyle(tint: .pink)) - - // 自定义样式 - ProgressView(value: v / 100) - .padding(.vertical) - .progressViewStyle(PCProgressStyle1(borderWidth: 3)) - - ProgressView(value: v / 100) - .progressViewStyle(PCProgressStyle2()) - .frame(height:200) - - Slider(value: $v, in: 0...100, step: 1) - } - .padding(20) - } -} - -// 自定义 Progress 样式 -struct PCProgressStyle1: ProgressViewStyle { - var lg = LinearGradient(colors: [.purple, .black, .blue], startPoint: .topLeading, endPoint: .bottomTrailing) - var borderWidth: Double = 2 - - func makeBody(configuration: Configuration) -> some View { - let fc = configuration.fractionCompleted ?? 0 - - return VStack { - ZStack(alignment: .topLeading) { - GeometryReader { g in - Rectangle() - .fill(lg) - .frame(maxWidth: g.size.width * CGFloat(fc)) - } - } - .frame(height: 20) - .cornerRadius(10) - .overlay( - RoundedRectangle(cornerRadius: 10) - .stroke(lg, lineWidth: borderWidth) - ) - // end ZStack - } // end VStack - } -} - -struct PCProgressStyle2: ProgressViewStyle { - var lg = LinearGradient(colors: [.orange, .yellow, .green, .blue, .purple], startPoint: .topLeading, endPoint: .bottomTrailing) - - var borderWidth: Double = 20 - - func makeBody(configuration: Configuration) -> some View { - let fc = configuration.fractionCompleted ?? 0 - - func strokeStyle(_ g: GeometryProxy) -> StrokeStyle { - StrokeStyle(lineWidth: 0.1 * min(g.size.width, g.size.height), lineCap: .round) - } - - return VStack { - GeometryReader { g in - ZStack { - Group { - Circle() - .trim(from: 0, to: 1) - .stroke(lg, style: strokeStyle(g)) - .padding(borderWidth) - .opacity(0.2) - Circle() - .trim(from: 0, to: fc) - .stroke(lg, style: strokeStyle(g)) - .padding(borderWidth) - } - .rotationEffect(.degrees(90 + 360 * 0.5), anchor: .center) - .offset(x: 0, y: 0.1 * min(g.size.width, g.size.height)) - } - - Text("读取 \(Int(fc * 100)) %") - .bold() - .font(.headline) - } - // end ZStack - } // end VStack - } -} -``` - -SwiftUI 引入一个新显示进度的视图 Gauge。 - -简单示例如下: -```swift -struct PGauge: View { - @State private var progress = 0.45 - var body: some View { - Gauge(value: progress) { - Text("进度") - } currentValueLabel: { - Text(progress.formatted(.percent)) - } minimumValueLabel: { - Text(0.formatted(.percent)) - } maximumValueLabel: { - Text(100.formatted(.percent)) - } - - Gauge(value: progress) { - - } currentValueLabel: { - Text(progress.formatted(.percent)) - .font(.footnote) - } - .gaugeStyle(.accessoryCircularCapacity) - .tint(.cyan) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/Blend Modes(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/Blend Modes(ap).md" deleted file mode 100644 index d205a5f8c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/Blend Modes(ap).md" +++ /dev/null @@ -1,83 +0,0 @@ - -Blend Modes - -### 介绍 - -`blendMode(_:)` 是 SwiftUI 中的一个视图修饰符,用于混合视图。以下是两种使用 `blendMode(_:)` 的方式,以及相应的示例: - -1. 通过 `ZStack` 使用 `blendMode(_:)`: - -```swift -struct ContentView: View { - var body: some View { - ZStack { - Image("evermore") - Image("fearless") - .blendMode(.multiply) - } - } -} -``` - -在这个例子中,我们创建了一个 `ZStack`,它包含两个图像。我们将 `blendMode(.multiply)` 应用于第二个图像,这样它就会与第一个图像混合。 - -2. 通过 `.overlay` 使用 `blendMode(_:)`: - -```swift -struct ContentView: View { - var body: some View { - Image("evermore") - .overlay { - Image("fearless") - .blendMode(.color) - .blendMode(.multiply) - } - } -} -``` - -在这个例子中,我们创建了一个图像,并添加了一个覆盖层。这个覆盖层是另一个图像,我们将 `blendMode(.multiply)` 应用于这个图像,这样它就会与底层的图像混合。 - -`.multiply` 是一种混合模式,你可以根据需要选择其他的混合模式。 - -blendMode 混合模式可以分为以下几种类型: - -## 变亮 - -提升亮部亮度 - -- `colorDodge` -- `lighten` -- `screen` -- `plusLighter` - -## 变暗 - -使暗部更暗 - -- `colorBurn` -- `darken` -- `multiply` -- `plusDarker` - -## 对比 - -让亮部更亮,暗部更暗,对比度增加,更艳丽。 - -- `overlay` -- `softLight` -- `hardLight` - -## 融合 - -这些模式会根据源图像和目标图像的色调、饱和度、颜色或亮度进行混合。 - -- `hue` -- `saturation` -- `color` -- `luminosity` -- `sourceAtop` -- `destinationOver` -- `destinationOut` -- `difference` -- `exclusion` \ No newline at end of file diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SF Symbol(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SF Symbol(ap).md" deleted file mode 100644 index f7f954a72..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SF Symbol(ap).md" +++ /dev/null @@ -1,168 +0,0 @@ - - -## 演进 - -在 iOS 13 中,苹果引入了 SF Symbols,这是一套专门为 Apple 设备设计的符号图标,可以在应用程序中使用。这些符号图标是矢量图形,可以在不同的大小和颜色下保持清晰度。 - -WWDC 21 符号数量达到 3000 个,包括 500 个全新的符号,以及 1000 个符号的变体。推出了 SF Symbols 3,支持更多的变体和颜色。可以自定义符号。 - -WWDC 22 推出了 SF Symbols 4,数量达到 4000 个,支持更多的变体和颜色。有自动渲染模式,还有可变颜色。 - -WWDC 23 推出了 SF Symbols 5,数量达到 5000 个。符号有7种动画,可以定制动画。可以用 Symbol Components 来创建自定义符号。 - -## 变量值 - -SF Symbol 支持变量值,可以通过设置 variableValue 来填充不同部分,比如 wifi 图标,不同值会亮不同部分,`Image(systemName: "wifi", variableValue: 0.5)` 。 - -下面是一个使用变量值的示例代码: - -```swift -struct ContentView: View { - @State private var variableValue: Double = 0.5 - - var body: some View { - VStack { - Slider(value: $variableValue, in: 0...1, step: 0.01) - Image(systemName: "speaker.wave.3.fill", variableValue: variableValue) - .symbolRenderingMode(.palette) - .symbolVariant(.fill) - .foregroundColor(.blue) - .imageScale(.large) - } - .padding() - } -} -``` - -## 大小 - -`.imageScale` 可以改变 SF Symbol 的大小,可以设置为 `.small`、`.medium`、`.large`。 - -```swift -struct ContentView: View { - var body: some View { - Image(systemName: "wifi") - .imageScale(.large) - } -} -``` - -还可以通过 `.font(.system(size:` 和 `.fontWeight(.semibold)` 来设置大小和粗细。 - -## 文本插值 - -文本插值支持 SF Symbol,可以在文本中插入 SF Symbol。 - -```swift -Text("这是一辆双层 \(Image(systemName: "bus.doubledecker"))") -``` - -## symbolRenderingMode 渲染模式 - -symbolRenderingMode 可以设置 SF Symbol 的渲染模式,可以设置为 `.multicolor`、`.palette`、`.monochrome`、`.hierarchical`。 - -- `.multicolor`:多色模式。这种模式下,SF Symbols 将使用预定义的多种颜色来显示。这是默认的渲染模式。 -- `.palette`:调色板模式。这种模式下,SF Symbols 将使用你指定的颜色来显示。你可以为每个部分指定不同的颜色。 -- `.monochrome`:单色模式。这种模式下,SF Symbols 将使用单一颜色来显示。这个颜色是你在代码中指定的颜色。 -- `.hierarchical`:层次模式。这种模式下,SF Symbols 将使用一种颜色,但是不同的部分会有不同的透明度。这可以创建出一种层次感。 - -以下是一个使用 `.multicolor` 渲染模式的示例代码: - -```swift -struct ContentView: View { - var body: some View { - Image(systemName: "figure.walk.motion.trianglebadge.exclamationmark") - .symbolRenderingMode(.multicolor) - } -} -``` - -## symbolVariant 变体 - -symbolVariant 可以设置 SF Symbol 的变体,可以设置为 `.none`、`.circle`、`.square`、`.rectangle`、`.fill`、`.slash`。 - -当然,以下是 SF Symbols 的各种变体的详细介绍: - -- `none`:没有任何特殊变体的 SF Symbol。 -- `circle`:在一个圆形背景中的。例如,`circle` 变体的 "person" symbol 就是一个人像在一个圆形背景中。 -- `square`:在一个正方形背景中的。例如,`square` 变体的 "person" symbol 就是一个人像在一个正方形背景中。 -- `rectangle`:在一个矩形背景中的。例如,`rectangle` 变体的 "person" symbol 就是一个人像在一个矩形背景中。 -- `fill`:填充的,也就是说,它的内部是有颜色的,而不是空心的。例如,`fill` 变体的 "heart" symbol 就是一个填充的心形。 -- `slash`:一条斜线穿过。例如,`slash` 变体的 "bell" symbol 就是一个有斜线穿过的铃铛,表示静音。 - -```swift -struct ContentView: View { - var body: some View { - VStack(alignment: .leading) { - Label("Default", systemImage: "person") - Label("Circle", systemImage: "circle") - .symbolVariant(.circle) - Label("Circle Fill", systemImage: "circle.fill") - .symbolVariant(.circle.fill) - Label("Square", systemImage: "square") - .symbolVariant(.square) - Label("Square Fill", systemImage: "square.fill") - .symbolVariant(.square.fill) - Label("Rectangle", systemImage: "rectangle") - .symbolVariant(.rectangle) - Label("Rectangle Fill", systemImage: "rectangle.fill") - .symbolVariant(.rectangle.fill) - Label("Fill", systemImage: "heart") - .symbolVariant(.fill) - Label("Slash", systemImage: "bell") - .symbolVariant(.slash) - Label("Slash Fill", systemImage: "bell.fill") - .symbolVariant(.slash.fill) - } - } -} - -``` - -## 动画 - - SF Symbols 的动画一共有 7 种,分别是:Appear, Disappear, Bounce, Scale, Variable Color, Pulse, Replace。 - -```swift -struct ContentView: View { - @State private var isAnimating = false - @State var count: Int = 0 - - var body: some View { - VStack { - Button("Toggle Animation") { - withAnimation { - self.isAnimating.toggle() - } - } - Stepper("Count: \(count)", value: $count, in: 0...100) - - Image(systemName: "moon.stars") - .symbolEffect(.disappear, isActive: isAnimating) - - Image(systemName: "sun.max.fill") - .symbolEffect( - .variableColor, - isActive: isAnimating - ) - Image(systemName: "wifi") - .symbolEffect(.bounce, value: count) - Image(systemName: "wifi") - .symbolEffect(.pulse, value: count) - Image(systemName: "wifi") - .symbolEffect(.variableColor, value: count) - Image(systemName: "wifi") - .symbolEffect(.variableColor, isActive: isAnimating) - Image(systemName: "wifi") - .symbolEffect(.scale.up, isActive: isAnimating) - Image(systemName: "wifi") - .symbolEffect(.scale.down, isActive: isAnimating) - Image(systemName: "wifi") - .symbolEffect(.pulse, isActive: isAnimating) - } - .imageScale(.large) - .padding() - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/Shaders Metal(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/Shaders Metal(ap).md" deleted file mode 100644 index 9ca06222c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/Shaders Metal(ap).md" +++ /dev/null @@ -1,4 +0,0 @@ - -Shaders Metal 是 Apple 的图形和计算任务编程接口的一部分。它允许开发者直接访问 GPU 的能力,以实现高性能的图形渲染和数据并行计算任务。 - -在 Metal 中,shader 是运行在 GPU 上的小程序,用于处理图形和计算任务。有两种主要类型的 shader:vertex shaders 和 fragment shaders。Vertex shaders 处理顶点数据,用于变换和光照等操作。Fragment shaders 处理像素数据,用于颜色和纹理等操作。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftCharts(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftCharts(ap).md" deleted file mode 100644 index c429939fd..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftCharts(ap).md" +++ /dev/null @@ -1,143 +0,0 @@ -可视化数据,使用 SwiftUI 语法来创建。还可以使用 ChartRenderer 接口将图标渲染成图。 - -官方文档 [Swift Charts](https://developer.apple.com/documentation/Charts) - -入门参看 [Hello Swift Charts](https://developer.apple.com/videos/play/wwdc2022/10136/) - -Apple 文章 [Creating a chart using Swift Charts](https://developer.apple.com/documentation/Charts/Creating-a-chart-using-Swift-Charts) - -高级定制和创建更精细图表,可以看这个 session [Swift Charts: Raise the bar](https://developer.apple.com/videos/play/wwdc2022/10137) 这个 session 也会提到如何在图表中进行交互。这里是 session 对应的代码示例 [Visualizing your app’s data](https://developer.apple.com/documentation/charts/visualizing_your_app_s_data) 。 - -图表设计的 session,[Design an effective chart](https://developer.apple.com/videos/play/wwdc2022-110340) 和 [Design app experiences with charts](https://developer.apple.com/videos/play/wwdc2022-110342) 。 - -下面是一个简单的代码示例: -```swift -import Charts - -struct PChartModel: Hashable { - var day: String - var amount: Int = .random(in: 1..<100) -} - -extension PChartModel { - static var data: [PChartModel] { - let calendar = Calendar(identifier: .gregorian) - let days = calendar.shortWeekdaySymbols - return days.map { day in - PChartModel(day: day) - } - } -} - -struct PlayCharts: View { - var body: some View { - Chart(PChartModel.data, id: \.self) { v in - BarMark(x: .value("天", v.day), y: .value("数量", v.amount)) - - } - .padding() - } -} - -struct PSwiftCharts: View { - struct CData: Identifiable { - let id = UUID() - let i: Int - let v: Double - } - - @State private var a: [CData] = [ - .init(i: 0, v: 2), - .init(i: 1, v: 20), - .init(i: 2, v: 3), - .init(i: 3, v: 30), - .init(i: 4, v: 8), - .init(i: 5, v: 80) - ] - - var body: some View { - Chart(a) { i in - LineMark(x: .value("Index", i.i), y: .value("Value", i.v)) - BarMark(x: .value("Index", i.i), yStart: .value("开始", 0), yEnd: .value("结束", i.v)) - .foregroundStyle(by: .value("Value", i.v)) - } // end Chart - } // end body -} -``` - -BarMark 用于创建条形图,LineMark 用于创建折线图。SwiftUI Charts 框架还提供 PointMark、AxisMarks、AreaMark、RectangularMark 和 RuleMark 用于创建不同类型的图表。注释使用 `.annotation` modifier,修改颜色可以使用 `.foregroundStyle` modifier。`.lineStyle` modifier 可以修改线宽。 - -AxisMarks 的示例如下: -```swift -struct MonthlySalesChart: View { - var body: some View { - Chart(data, id: \.month) { - BarMark( - x: .value("Month", $0.month, unit: .month), - y: .value("Sales", $0.sales) - ) - } - .chartXAxis { - AxisMarks(values: .stride(by: .month)) { value in - if value.as(Date.self)!.isFirstMonthOfQuarter { - AxisGridLine().foregroundStyle(.black) - AxisTick().foregroundStyle(.black) - AxisValueLabel( - format: .dateTime.month(.narrow) - ) - } else { - AxisGridLine() - } - } - } - } -} -``` - -可交互图表示例如下: -```swift -struct InteractiveBrushingChart: View { - @State var range: (Date, Date)? = nil - - var body: some View { - Chart { - ForEach(data, id: \.day) { - LineMark( - x: .value("Month", $0.day, unit: .day), - y: .value("Sales", $0.sales) - ) - .interpolationMethod(.catmullRom) - .symbol(Circle().strokeBorder(lineWidth: 2)) - } - if let (start, end) = range { - RectangleMark( - xStart: .value("Selection Start", start), - xEnd: .value("Selection End", end) - ) - .foregroundStyle(.gray.opacity(0.2)) - } - } - .chartOverlay { proxy in - GeometryReader { nthGeoItem in - Rectangle().fill(.clear).contentShape(Rectangle()) - .gesture(DragGesture() - .onChanged { value in - // Find the x-coordinates in the chart’s plot area. - let xStart = value.startLocation.x - nthGeoItem[proxy.plotAreaFrame].origin.x - let xCurrent = value.location.x - nthGeoItem[proxy.plotAreaFrame].origin.x - // Find the date values at the x-coordinates. - if let dateStart: Date = proxy.value(atX: xStart), - let dateCurrent: Date = proxy.value(atX: xCurrent) { - range = (dateStart, dateCurrent) - } - } - .onEnded { _ in range = nil } // Clear the state on gesture end. - ) - } - } - } -} -``` - -社区做的更多 Swift Charts 范例 [Swift Charts Examples](https://github.com/jordibruin/Swift-Charts-Examples) 。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI Canvas(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI Canvas(ap).md" deleted file mode 100644 index e9610a6a9..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI Canvas(ap).md" +++ /dev/null @@ -1,140 +0,0 @@ -Canvas 可以画路径、图片和文字、Symbols、可变的图形上下文、使用 CoreGraphics 代码和做动画。 - -图形上下文可以被 addFilter、clip、clipToLayer、concatenate、rotate、scaleBy、translateBy 这些方法来进行改变。 - -示例代码如下: - -```swift -struct PlayCanvas: View { - let colors: [Color] = [.purple, .blue, .yellow, .pink] - - var body: some View { - - // 画路径 - PCCanvasPathView(t: .rounded) - PCCanvasPathView(t: .ellipse) - PCCanvasPathView(t: .circle) - - // 图片和文字 - PCCanvasImageAndText(text: "Starming", colors: [.purple, .pink]) - - // Symbol,在 Canvas 里引用 SwiftUI 视图 - Canvas { c, s in - let c0 = c.resolveSymbol(id: 0)! - let c1 = c.resolveSymbol(id: 1)! - let c2 = c.resolveSymbol(id: 2)! - let c3 = c.resolveSymbol(id: 3)! - - c.draw(c0, at: .init(x: 10, y: 10), anchor: .topLeading) - c.draw(c1, at: .init(x: 30, y: 20), anchor: .topLeading) - c.draw(c2, at: .init(x: 50, y: 30), anchor: .topLeading) - c.draw(c3, at: .init(x: 70, y: 40), anchor: .topLeading) - - } symbols: { - ForEach(Array(colors.enumerated()), id: \.0) { i, c in - Circle() - .fill(c) - .frame(width: 100, height: 100) - .tag(i) - } - } - - // Symbol 动画和 SwiftUI 视图一样,不会受影响 - Canvas { c, s in - let sb = c.resolveSymbol(id: 0)! - c.draw(sb, at: CGPoint(x: s.width / 2, y: s.height / 2), anchor: .center) - - } symbols: { - PCForSymbolView() - .tag(0) - } - } // end var body -} - -// MARK: - 给 Symbol 用的视图 -struct PCForSymbolView: View { - @State private var change = true - var body: some View { - Image(systemName: "star.fill") - .renderingMode(.original) - .font(.largeTitle) - .rotationEffect(.degrees(change ? 0 : 72)) - .onAppear { - withAnimation(.linear(duration: 1.0).repeatForever(autoreverses: false)) { - change.toggle() - } - } - } -} - -// MARK: - 图片和文字 -struct PCCanvasImageAndText: View { - let text: String - let colors: [Color] - var fontSize: Double = 42 - - var body: some View { - Canvas { context, size in - let midPoint = CGPoint(x: size.width / 2, y: size.height / 2) - let font = Font.system(size: fontSize) - var resolved = context.resolve(Text(text).font(font)) - - let start = CGPoint(x: (size.width - resolved.measure(in: size).width) / 2.0, y: 0) - let end = CGPoint(x: size.width - start.x, y: 0) - - resolved.shading = .linearGradient(Gradient(colors: colors), startPoint: start, endPoint: end) - context.draw(resolved, at: midPoint, anchor: .center) - - } - } -} - -// MARK: - Path -struct PCCanvasPathView: View { - enum PathType { - case rounded, ellipse, casual, circle - } - let t: PathType - - var body: some View { - Canvas { context, size in - - conf(context: &context, size: size, type: t) - } // end Canvas - } - - func conf( context: inout GraphicsContext, size: CGSize, type: PathType) { - let rect = CGRect(origin: .zero, size: size).insetBy(dx: 25, dy: 25) - var path = Path() - switch type { - case .rounded: - path = Path(roundedRect: rect, cornerRadius: 35.0) - case .ellipse: - let cgPath = CGPath(ellipseIn: rect, transform: nil) - path = Path(cgPath) - case .casual: - path = Path { - let points: [CGPoint] = [ - .init(x: 10, y: 10), - .init(x: 0, y: 50), - .init(x: 100, y: 100), - .init(x: 100, y: 0), - ] - $0.move(to: .zero) - $0.addLines(points) - } - case .circle: - path = Circle().path(in: rect) - } - - - let gradient = Gradient(colors: [.purple, .pink]) - let from = rect.origin - let to = CGPoint(x: rect.width, y: rect.height + from.y) - - // Stroke path - context.stroke(path, with: .color(.blue), lineWidth: 25) - context.fill(path, with: .linearGradient(gradient, startPoint: from, endPoint: to)) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI Effect(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI Effect(ap).md" deleted file mode 100644 index f02788f3f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI Effect(ap).md" +++ /dev/null @@ -1,73 +0,0 @@ -![](https://ming1016.github.io/qdimg/240505/swiftuieffect-ap01.jpeg) - -```swift -struct PlayEffect: View { - @State private var isHover = false - - var body: some View { - ZStack { - LinearGradient(colors: [.purple, .black, .pink], startPoint: .top, endPoint: .bottom).ignoresSafeArea() - - VStack(spacing: 20) { - - // 材质 - Text("材质效果") - .font(.system(size:30)) - .padding(isHover ? 40 : 30) - .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous)) - .onHover { b in - withAnimation { - isHover = b - } - } - - // 模糊 - Text("模糊效果") - .font(.system(size: 30)) - .padding(30) - .background { - Color.black.blur(radius: 8, opaque: false) - } - - // 选择 - Text("3D 旋转") - .font(.largeTitle) - .rotation3DEffect(Angle(degrees: 45), axis: (x: 0, y: 20, z: 0)) - .scaleEffect(1.5) - .blendMode(.hardLight) - .blur(radius: 3) - - } - - } - } -} -``` - -材质厚度从低到高有: - -* .regularMaterial -* .thinMaterial -* .ultraThinMaterial -* .thickMaterial -* .ultraThickMaterial - -Gradient 和 Shadow 的 2022 的更新 - -下面是个简单示例: -```swift -struct PGradientAndShadow: View { - var body: some View { - Image(systemName: "bird") - .frame(width: 150, height: 150) - .background(in: Rectangle()) - .backgroundStyle(.cyan.gradient) - .foregroundStyle(.white.shadow(.drop(radius: 1, y: 3.0))) - .font(.system(size: 60)) - } -} -``` - -Paul Hudson 使用 Core Motion 做了一个阴影随设备倾斜而变化的效果,非常棒,[How to use inner shadows to simulate depth with SwiftUI and Core Motion](https://www.hackingwithswift.com/articles/253/how-to-use-inner-shadows-to-simulate-depth-with-swiftui-and-core-motion) 。 - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-Shadow(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-Shadow(ap).md" deleted file mode 100644 index d0cf92905..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-Shadow(ap).md" +++ /dev/null @@ -1,67 +0,0 @@ - - -## shadow - -卡片阴影效果 - -```swift -.shadow(color: Color(.sRGB, red: 0, green: 0, blue: 0, opacity: 0.25), radius: 10, x: 0, y: 0) -``` - -## `.shadow(.drop(radius:` 前景阴影 - -```swift -struct ContentView: View { - var body: some View { - Image(systemName: "film") - .frame(width: 200, height: 200) - .background(Color.mint) - .foregroundStyle(Color.white.shadow(.drop(radius: 1, y: 2.0))) - .font(.system(size: 80)) - .overlay { - VStack { - Spacer() - Text("电影") - .font(.largeTitle) - .foregroundStyle(Color.white.shadow(.drop(radius: 1, y: 2.0))) - .padding() - } - } - } -} -``` - -以上代码中,我们使用了 `shadow(.drop(radius: y:))` 修饰符为图像和文本添加了阴影效果。这个修饰符接受两个参数:`radius` 和 `y`。`radius` 参数控制阴影的模糊半径,`y` 参数控制阴影的偏移量。 - - -## 多重阴影,发光效果 - -```swift -struct ContentView: View { - var body: some View { - ZStack { - Rectangle() - .foregroundStyle(.black) - Image(systemName: "film") - .frame(width: 200, height: 200) - .background(Color.mint) - .foregroundStyle(Color.white.shadow(.drop(radius: 2, y: 4.0))) - .shadow(color: .white.opacity(0.3), radius: 100) - .shadow(color: .white.opacity(0.5), radius: 80) - .shadow(color: .white.opacity(0.6), radius: 30) - .shadow(color: .white.opacity(0.7), radius: 10) - .font(.system(size: 80)) - .overlay { - VStack { - Spacer() - Text("电影") - .font(.largeTitle) - .foregroundStyle(Color.white.shadow(.drop(radius: 2, y: 4.0))) - .padding() - } - } - } - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\346\250\241\347\263\212(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\346\250\241\347\263\212(ap).md" deleted file mode 100644 index 0ba46d506..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\346\250\241\347\263\212(ap).md" +++ /dev/null @@ -1,5 +0,0 @@ - -三方库 - -- [daprice/Variablur: Variable blur effects for SwiftUI, powered by Metal](https://github.com/daprice/Variablur) -- [joogps/Glur: Progressive blurs in SwiftUI.](https://github.com/joogps/Glur) diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\346\270\220\345\217\230(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\346\270\220\345\217\230(ap).md" deleted file mode 100644 index 6ca2dad92..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\346\270\220\345\217\230(ap).md" +++ /dev/null @@ -1,96 +0,0 @@ - -## `.gradient` - -在 SwiftUI 中,你可以使用 `.gradient` 修饰符为任何 `Color` 添加渐变效果。例如,你可以使用 `.gradient` 修饰符为 `Text` 添加渐变效果,如下所示: - -Color 后直接加 `.gradient` 可以实现渐变效果,如下所示: -```swift -struct ContentView: View { - var body: some View { - ZStack { - Rectangle() - .fill(.white.gradient) - Text("渐变色") - .font(.largeTitle) - .bold() - .foregroundStyle(.indigo.gradient) - } - } -} -``` - -## Gradient - -你可以使用 `Gradient` 类型来创建自定义渐变,如下所示: - -```swift -struct ContentView: View { - let gradientColors: [Color] = [ - .mint, .yellow - ] - - var body: some View { - ZStack { - Rectangle() - .fill(.white.gradient) - Text("渐变色") - .font(.largeTitle) - .bold() - .foregroundStyle(Gradient(colors: gradientColors)) - } - } -} -``` - -## LinearGradient - -你可以使用 `LinearGradient` 类型创建线性渐变,如下所示: - -```swift -Rectangle() - .fill(LinearGradient(colors: [.white, .black], startPoint: .leading, endPoint: .trailing)) -``` - -## RadialGradient - -你可以使用 `RadialGradient` 类型创建径向渐变,如下所示: - -```swift -struct ContentView: View { - var body: some View { - Rectangle() - .fill( - RadialGradient( - gradient: Gradient(colors: [.black, .gray, .white]), - center: .center, - startRadius: 0, - endRadius: 200 - ) - ) - .frame(width: 400, height: 600) - .cornerRadius(20) - } -} -``` - -我们创建了一个矩形,并使用 `RadialGradient` 填充它。渐变从黑色开始,经过深灰色和浅灰色,最后到白色,从中心开始,到半径为 200 的圆结束。我们还设置了矩形的宽度和高度分别为 400 和 600,以及圆角半径为 20。 - -## AngularGradient - -`AngularGradient` 类型创建一个角度渐变,如下所示: - -```swift -struct ContentView: View { - var body: some View { - Rectangle() - .fill( - AngularGradient( - gradient: Gradient(colors: [.black, .gray, .white, .black]), - center: .center - ) - ) - .frame(width: 400, height: 600) - .cornerRadius(20) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\350\203\214\346\231\257\346\235\220\350\264\250(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\350\203\214\346\231\257\346\235\220\350\264\250(ap).md" deleted file mode 100644 index f0cc4b5bd..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI-\350\203\214\346\231\257\346\235\220\350\264\250(ap).md" +++ /dev/null @@ -1,77 +0,0 @@ - -背景材质使用例子 - -```swift -struct ContentView: View { - var body: some View { - ZStack { - Image("evermore") - Text("电影标题") - .font(.largeTitle) - .foregroundStyle(.white) - .padding() - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous)) - } - } -} -``` - -材质厚度从低到高: - -- .ultraThinMaterial -- .thinMaterial -- .regularMaterial -- .bar -- .thickMaterial -- .ultraThickMaterial - -下面例子可以直观看到效果 - -```swift -struct ContentView: View { - var body: some View { - ZStack { - Rectangle() - .fill(.indigo.gradient) - VStack { - Text("材质").font(.largeTitle).padding() - .background(.ultraThinMaterial) - Text("材质").font(.largeTitle).padding() - .background(.thinMaterial) - Text("材质").font(.largeTitle).padding() - .background(.regularMaterial) - Text("材质").font(.largeTitle).padding() - .background(.bar) - Text("材质").font(.largeTitle).padding() - .background(.thickMaterial) - Text("材质").font(.largeTitle).padding() - .background(.ultraThickMaterial) - } - } - } -} -``` - -可以通过 opacity 和 brightness 来达成一些和背景融合的效果 - -```swift -struct ContentView: View { - var body: some View { - ZStack { - Rectangle() - .fill(.indigo.gradient) - VStack { - Text("融合效果").font(.largeTitle).padding() - .foregroundStyle(.white) - .background { - RoundedRectangle(cornerRadius: 12) - .foregroundStyle(.black) - .opacity(0.25) - .brightness(-0.4) - } - } - } - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI\351\242\234\350\211\262(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI\351\242\234\350\211\262(ap).md" deleted file mode 100644 index a59d74071..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/SwiftUI/\350\247\206\350\247\211/SwiftUI\351\242\234\350\211\262(ap).md" +++ /dev/null @@ -1,48 +0,0 @@ -```swift -struct PlayColor: View { - var body: some View { - ZStack { - Color.black.edgesIgnoringSafeArea(.all) // Color 也是一个 View - - VStack(spacing: 10) { - Text("这是一个适配了暗黑的文字颜色") - .foregroundColor(light: .purple, dark: .pink) - .background(Color(nsColor: .quaternaryLabelColor)) // 使用以前 NSColor - - Text("自定义颜色") - .foregroundColor(Color(red: 0, green: 0, blue: 100)) - } - .padding() - - } - } -} - -// MARK: - 暗黑适配颜色 -struct PCColorModifier: ViewModifier { - @Environment(\.colorScheme) private var colorScheme - var light: Color - var dark: Color - - private var adaptColor: Color { - switch colorScheme { - case .light: - return light - case .dark: - return dark - @unknown default: - return light - } - } - - func body(content: Content) -> some View { - content.foregroundColor(adaptColor) - } -} - -extension View { - func foregroundColor(light: Color, dark: Color) -> some View { - modifier(PCColorModifier(light: light, dark: dark)) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\345\205\261\344\272\253\350\217\234\345\215\225(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\345\205\261\344\272\253\350\217\234\345\215\225(ap).md" deleted file mode 100644 index d63823aee..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\345\205\261\344\272\253\350\217\234\345\215\225(ap).md" +++ /dev/null @@ -1,29 +0,0 @@ -```swift -struct ShareView: View { - var s: String - var body: some View { - Menu { - Button { - let p = NSPasteboard.general - p.declareTypes([.string], owner: nil) - p.setString(s, forType: .string) - } label: { - Text("拷贝链接") - Image(systemName: "doc.on.doc") - } - Divider() - ForEach(NSSharingService.sharingServices(forItems: [""]), id: \.title) { item in - Button { - item.perform(withItems: [s]) - } label: { - Text(item.title) - Image(nsImage: item.image) - } - } - } label: { - Text("分享") - Image(systemName: "square.and.arrow.up") - } - } -} -``` \ No newline at end of file diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\345\211\252\350\264\264\346\235\277(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\345\211\252\350\264\264\346\235\277(ap).md" deleted file mode 100644 index d24609eb8..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\345\211\252\350\264\264\346\235\277(ap).md" +++ /dev/null @@ -1,14 +0,0 @@ -添加和读取剪贴板的方法如下: -```swift -// 读取剪贴板内容 -let s = NSPasteboard.general.string(forType: .string) -guard let s = s else { - return -} -print(s) - -// 设置剪贴板内容 -let p = NSPasteboard.general -p.declareTypes([.string], owner: nil) -p.setString(s, forType: .string) -``` \ No newline at end of file diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" deleted file mode 100644 index b040b68a8..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" +++ /dev/null @@ -1,90 +0,0 @@ -支持了 window,可以控制位置和大小。官方代码示例 [Bringing multiple windows to your SwiftUI app](https://developer.apple.com/documentation/swiftui/bringing_multiple_windows_to_your_swiftui_app) - -openWindow 代码示例如下: -```swift -struct PartyPlanner: App { - var body: some Scene { - WindowGroup("Party Planner") { - PartyPlannerHome() - } - - Window("Party Budget", id: "budget") { - Text("Budget View") - } - .keyboardShortcut("0") - .defaultPosition(.topLeading) - .defaultSize(width: 220, height: 250) - } -} - -struct DetailView: View { - @Environment(\.openWindow) var openWindow - - var body: some View { - Text("Detail View") - .toolbar { - Button { - openWindow(id: "budget") - } label: { - Image(systemName: "dollarsign") - } - } - } -} -``` - -session [Bring multiple windows to your SwiftUI app](https://developer.apple.com/videos/play/wwdc2022-10061) 两个新 Scene 类型。WindowGroup 允许多 window。MenuBarExtra。可编程方式打开新 window 和 document。 - -MenuBarExtra 代码示例如下: -```swift -struct PartyPlanner: App { - var body: some Scene { - Window("Party Budget", id: "budget") { - Text("Budget View") - } - - MenuBarExtra("Bulletin Board", systemImage: "quote.bubble") { - BulletinBoard() - } - .menuBarExtraStyle(.window) - } -} -``` - -讲和 AppKit 混编的 session [Use SwiftUI with AppKit](https://developer.apple.com/videos/play/wwdc2022/10075/) - -[The craft of SwiftUI API design: Progressive disclosure](https://developer.apple.com/videos/play/wwdc2022-10059) 使用 windows 还有 MenuBarExtra,使用 modifier 来自定义应用程序 window 的 presentation 和行为。 - -使用 `.dropDestination` 来支持拖动。示例如下: -```swift -.dropDestination(payloadType: Image.self) { receivedImages, location in - guard let image = receivedImages.first else { - return false - } - viewModel.imageState = .success(image) - return true - } -``` - -今年有新的 [FormStyle](https://developer.apple.com/documentation/swiftui/formstyle/columns) ,示例如下: -```swift -Form { - Picker("Notify Me About:", selection: $notifyMeAbout) { - Text("Direct Messages").tag(NotifyMeAboutType.directMessages) - Text("Mentions").tag(NotifyMeAboutType.mentions) - Text("Anything").tag(NotifyMeAboutType.anything) - } - Toggle("Play notification sounds", isOn: $playNotificationSounds) - Toggle("Send read receipts", isOn: $sendReadReceipts) - - Picker("Profile Image Size:", selection: $profileImageSize) { - Text("Large").tag(ProfileImageSize.large) - Text("Medium").tag(ProfileImageSize.medium) - Text("Small").tag(ProfileImageSize.small) - } - .pickerStyle(.inline) -} -.formStyle(.columns) -``` - -Apple 自身在 macOS 系统中使用了多少 SwiftUI 呢?邮件、iWork 和 Keychain Access 的部分视图使用了,笔记、照片 和 Xcode 部分功能及新增功能的完整界面都是用的 SwiftUI,另外控制中心、字体册和系统设置的大部分都是用 SwiftUI 开发了。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\350\214\203\344\276\213(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\350\214\203\344\276\213(ap).md" deleted file mode 100644 index 526af7e13..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/macOS\350\214\203\344\276\213(ap).md" +++ /dev/null @@ -1,2 +0,0 @@ -- 官方提供的两个例子,[Creating a macOS App](https://developer.apple.com/tutorials/swiftui/creating-a-macos-app),[Building a Great Mac App with SwiftUI](https://developer.apple.com/documentation/swiftui/building_a_great_mac_app_with_swiftui) (有table和LazyVGrid的用法)。 -- [GitHub - adamayoung/Movies: Movies and TV Shows App for iOS, iPadOS, watchOS and macOS](https://github.com/adamayoung/Movies) 使用了SwiftUI和Combine,电影数据使用的是[The Movie Database (TMDB)](https://www.themoviedb.org/)的API \ No newline at end of file diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/\344\270\211\346\240\217\347\273\223\346\236\204(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/macOS/\344\270\211\346\240\217\347\273\223\346\236\204(ap).md" deleted file mode 100644 index b94a36804..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/\344\270\211\346\240\217\347\273\223\346\236\204(ap).md" +++ /dev/null @@ -1,74 +0,0 @@ -三栏结构架子搭建,代码如下: - -```swift -import SwiftUI -struct SwiftPamphletApp: View { - var body: some View { - NavigationView { - SPSidebar() - Text("第二栏") - Text("第三栏") - } - .navigationTitle("Swift 小册子") - .toolbar { - ToolbarItem(placement: ToolbarItemPlacement.navigation) { - Button { - NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) - } label: { - Label("Sidebar", systemImage: "sidebar.left") - } - } - } - } -} -struct SPSidebar: View { - var body: some View { - List { - Section("第一组") { - NavigationLink("第一项", destination: SPList(title: "列表1")) - .badge(3) - NavigationLink("第二项", destination: SPList(title: "列表2")) - } - Section("第二组") { - NavigationLink("第三项", destination: SPList(title: "列表3")) - NavigationLink("第四项", destination: SPList(title: "列表4")) - } - } - .listStyle(SidebarListStyle()) - .frame(minWidth: 160) - .toolbar { - ToolbarItem { - Menu { - Text("1") - Text("2") - } label: { - Label("Label", systemImage: "slider.horizontal.3") - } - } - } - } -} -struct SPList: View { - var title: String - @State var searchText: String = "" - var body: some View { - List(0..<3) { i in - Text("内容\(i)") - } - .toolbar(content: { - Button { - // - } label: { - Label("Add", systemImage: "plus") - } - }) - .navigationTitle(title) - .navigationSubtitle("副标题") - .searchable(text: $searchText) - } -} -``` - -显示效果如下: -![](https://ming1016.github.io/qdimg/240505/threecolumnstructure-ap01.png) - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/\345\205\250\345\261\217\346\250\241\345\274\217(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/macOS/\345\205\250\345\261\217\346\250\241\345\274\217(ap).md" deleted file mode 100644 index 5118172fe..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/macOS/\345\205\250\345\261\217\346\250\241\345\274\217(ap).md" +++ /dev/null @@ -1,42 +0,0 @@ -将 NSSplitView 里的其中一个 NSView 设置为全屏和退出全屏的函数如下: - -```swift -// MARK: - 获取 NSSplitViewController -func splitVC() -> NSSplitViewController { - return ((NSApp.keyWindow?.contentView?.subviews.first?.subviews.first?.subviews.first as? NSSplitView)?.delegate as? NSSplitViewController)! -} - -// MARK: - 全屏 -func fullScreen(isEnter: Bool) { - if isEnter == true { - // 进入全屏 - let presOptions: - NSApplication.PresentationOptions = ([.autoHideDock,.autoHideMenuBar]) - let optionsDictionary = [NSView.FullScreenModeOptionKey.fullScreenModeApplicationPresentationOptions : NSNumber(value: presOptions.rawValue)] - - let v = splitVC().splitViewItems[2].viewController.view - v.enterFullScreenMode(NSScreen.main!, withOptions: optionsDictionary) - v.wantsLayer = true - } else { - // 退出全屏 - NSApp.keyWindow?.contentView?.exitFullScreenMode() - } // end if -} -``` - -使用方法 - -```swift -struct V: View { - @StateObject var appVM = AppVM() - @State var isEnterFullScreen: Bool = false // 全屏控制 - var body: some View { - Button { - isEnterFullScreen.toggle() - appVM.fullScreen(isEnter: isEnterFullScreen) - } label: { - Image(systemName: isEnterFullScreen == true ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right") - } - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\344\270\211\346\226\271\345\272\223\344\275\277\347\224\250/SQLite.swift\347\232\204\344\275\277\347\224\250(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\344\270\211\346\226\271\345\272\223\344\275\277\347\224\250/SQLite.swift\347\232\204\344\275\277\347\224\250(ap).md" deleted file mode 100644 index d70546c5a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\344\270\211\346\226\271\345\272\223\344\275\277\347\224\250/SQLite.swift\347\232\204\344\275\277\347\224\250(ap).md" +++ /dev/null @@ -1,184 +0,0 @@ -下面是 SQLite.swift 库的使用介绍,包括了数据库创建,表创建,表的添加、更新、删除、查找等处理方法 - -```swift -import SQLite - -struct DB { - static let shared = DB() - static let path = NSSearchPathForDirectoriesInDomains( - .applicationSupportDirectory, .userDomainMask, true - ).first! - let BBDB: Connection? - private init() { - do { - print(DB.path) - BBDB = try Connection("\(DB.path)/github.sqlite3") - - } catch { - BBDB = nil - } - /// Swift 类型和 SQLite 类型对标如下: - /// Int64 = INTEGER - /// Double = REAL - /// String = TEXT - /// nil = NULL - /// SQLite.Blob = BLOB - - } - - // 创建表 - func cTbs() throws { - do { - try ReposNotiDataHelper.createTable() - try DevsNotiDataHelper.createTable() - } catch { - throw DBError.connectionErr - } - } - -} - -enum DBError: Error { - case connectionErr, insertErr, deleteErr, searchErr, updateErr, nilInData -} - -protocol DataHelperProtocol { - associatedtype T - static func createTable() throws -> Void - static func insert(i: T) throws -> Int64 - static func delete(i: T) throws -> Void - static func findAll() throws -> [T]? -} - -// MARK: 开发者更新提醒 -typealias DBDevNoti = ( - login: String, - lastReadId: String, - unRead: Int -) - -struct DevsNotiDataHelper: DataHelperProtocol { - static let table = Table("devsNoti") - static let login = Expression("login") - static let lastReadId = Expression("lastReadId") - static let unRead = Expression("unRead") - typealias T = DBDevNoti - - static func createTable() throws { - guard let db = DB.shared.BBDB else { - throw DBError.connectionErr - } - do { - let _ = try db.run(table.create(ifNotExists: true) { t in - t.column(login, unique: true) - t.column(lastReadId, defaultValue: "") - t.column(unRead, defaultValue: 0) - }) - } catch _ { - throw DBError.connectionErr - } - } // end createTable - - static func insert(i: DBDevNoti) throws -> Int64 { - guard let db = DB.shared.BBDB else { - throw DBError.connectionErr - } - let insert = table.insert(login <- i.login, lastReadId <- i.lastReadId, unRead <- i.unRead) - do { - let rowId = try db.run(insert) - guard rowId > 0 else { - throw DBError.insertErr - } - return rowId - } catch { - throw DBError.insertErr - } - } // end insert - - static func delete(i: DBDevNoti) throws { - guard let db = DB.shared.BBDB else { - throw DBError.connectionErr - } - let query = table.filter(login == i.login) - do { - let tmp = try db.run(query.delete()) - guard tmp == 1 else { - throw DBError.deleteErr - } - } catch { - throw DBError.deleteErr - } - } // end delete - - static func find(sLogin: String) throws -> DBDevNoti? { - guard let db = DB.shared.BBDB else { - throw DBError.connectionErr - } - let query = table.filter(login == sLogin) - let items = try db.prepare(query) - for i in items { - return DBDevNoti(login: i[login], lastReadId: i[lastReadId], unRead: i[unRead]) - } - return nil - } // end find - - static func update(i: DBDevNoti) throws { - guard let db = DB.shared.BBDB else { - throw DBError.connectionErr - } - let query = table.filter(login == i.login) - do { - if try db.run(query.update(lastReadId <- i.lastReadId, unRead <- i.unRead)) > 0 { - - } else { - throw DBError.updateErr - } - } catch { - throw DBError.updateErr - } - } // end update - - static func findAll() throws -> [DBDevNoti]? { - guard let db = DB.shared.BBDB else { - throw DBError.connectionErr - } - var arr = [DBDevNoti]() - let items = try db.prepare(table) - for i in items { - arr.append(DBDevNoti(login: i[login], lastReadId: i[lastReadId], unRead: i[unRead])) - } - return arr - } // end find all - -} - -``` - -使用时,可以在初始化时这么做: -```swift -// MARK: 初始化数据库 -et db = DB.shared -do { - try db.cTbs() // 创建表 -} catch { - -} -``` - -使用的操作示例如下: -```swift -do { - if let fd = try ReposNotiDataHelper.find(sFullName: r.id) { - reposDic[fd.fullName] = fd.unRead - } else { - do { - let _ = try ReposNotiDataHelper.insert(i: DBRepoNoti(fullName: r.id, lastReadCommitSha: "", unRead: 0)) - reposDic[r.id] = 0 - } catch { - return reposDic - } - } -} catch { - return reposDic -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/KeyframeAnimator(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/KeyframeAnimator(ap).md" deleted file mode 100644 index c8d055f9f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/KeyframeAnimator(ap).md" +++ /dev/null @@ -1,115 +0,0 @@ - -`KeyframeAnimator`是一个在SwiftUI中创建关键帧动画的工具。关键帧动画是一种动画类型,其中定义了动画开始和结束的关键帧,以及可能的一些中间关键帧,然后动画系统会在这些关键帧之间进行插值以创建平滑的动画。 - -`KeyframeAnimator`接受一个初始值,一个内容闭包,以及一个关键帧闭包。初始值是一个包含了动画所需的所有属性的结构(在这个例子中是`scale`,`rotation`和`offset`)。内容闭包接受一个这样的结构实例,并返回一个视图。这个视图将使用结构中的值进行配置,以便它可以根据这些值进行动画。关键帧闭包接受一个这样的结构实例,并定义了一系列的关键帧轨道。每个轨道都对应于结构中的一个属性,并定义了一系列的关键帧。每个关键帧都定义了一个值和一个时间点,动画系统将在这些关键帧之间进行插值。 - -此外,SwiftUI提供了四种不同类型的关键帧:`LinearKeyframe`,`SpringKeyframe`,`CubicKeyframe`和`MoveKeyframe`。前三种关键帧使用不同的动画过渡函数进行插值,而`MoveKeyframe`则立即跳转到指定值,无需插值。 - -`KeyframeAnimator`可以用于创建各种复杂的动画效果,例如根据滚动位置调整关键帧驱动的效果,或者根据时间进行更新。 - - -```swift -struct ContentView: View { - @State var animationTrigger: Bool = false - - var body: some View { - VStack { - KeyframeAnimator( - initialValue: AnimatedMovie(), - content: { movie in - Image("evermore") - .resizable() - .frame(width: 100, height: 150) - .scaleEffect(movie.scaleRatio) - .rotationEffect(movie.rotationAngle) - .offset(y: movie.verticalOffset) - }, keyframes: { movie in - KeyframeTrack(\.scaleRatio) { - LinearKeyframe(1.0, duration: 0.36) - SpringKeyframe(1.5, duration: 0.8, spring: .bouncy) - SpringKeyframe(1.0, spring: .bouncy) - } - - KeyframeTrack(\.rotationAngle) { - CubicKeyframe(.degrees(-30), duration: 1.0) - CubicKeyframe(.zero, duration: 1.0) - } - - KeyframeTrack(\.verticalOffset) { - LinearKeyframe(0.0, duration: 0.1) - SpringKeyframe(20.0, duration: 0.15, spring: .bouncy) - CubicKeyframe(-60.0, duration: 0.2) - MoveKeyframe(0.0) - } - } - ) - } - } -} - -struct AnimatedMovie { - var scaleRatio: Double = 1 - var rotationAngle = Angle.zero - var verticalOffset: Double = 0 -} -``` - -以上代码中,我们首先定义了一个`AnimatedMovie`结构,它包含了动画所需的所有属性。然后,我们在`ContentView`视图中创建了一个`KeyframeAnimator`,该修饰符接受一个观测值`animationTrigger`,用于触发动画。在`content`闭包中,我们使用`Image`视图创建了一个电影海报,并根据`AnimatedMovie`结构中的值对其进行配置。在`keyframes`闭包中,我们为每个属性定义了一系列的关键帧轨道。例如,我们为`scaleRatio`属性定义了三个关键帧,分别使用`LinearKeyframe`和`SpringKeyframe`进行插值。我们还为`rotationAngle`和`verticalOffset`属性定义了两个关键帧轨道,分别使用`CubicKeyframe`和`MoveKeyframe`进行插值。 - - -也可以使用 `.keyframeAnimator` 修饰符来创建关键帧动画。以下是一个示例,演示了如何使用 .keyframeAnimator 修饰符创建一个关键帧动画,该动画在用户点击时触发。 - -```swift -struct ContentView: View { - @State var animationTrigger: Bool = false - - var body: some View { - Image("evermore") - .resizable() - .frame(width: 100, height: 150) - .scaleEffect(animationTrigger ? 1.5 : 1.0) - .rotationEffect(animationTrigger ? .degrees(-30) : .zero) - .offset(y: animationTrigger ? -60.0 : 0.0) - .keyframeAnimator(initialValue: AnimatedMovie(), - trigger: animationTrigger, - content: { view, value in - view - .scaleEffect(value.scaleRatio) - .rotationEffect(value.rotationAngle) - }, - keyframes: { value in - KeyframeTrack(\.scaleRatio) { - LinearKeyframe(1.5, duration: 0.36) - SpringKeyframe(1.0, duration: 0.8, spring: .bouncy) - SpringKeyframe(1.5, spring: .bouncy) - } - - KeyframeTrack(\.rotationAngle) { - CubicKeyframe(.degrees(-30), duration: 1.0) - CubicKeyframe(.zero, duration: 1.0) - } - - KeyframeTrack(\.verticalOffset) { - LinearKeyframe(-60.0, duration: 0.1) - SpringKeyframe(0.0, duration: 0.15, spring: .bouncy) - CubicKeyframe(-60.0, duration: 0.2) - MoveKeyframe(0.0) - } - }) - - .onTapGesture { - withAnimation { - animationTrigger.toggle() - } - } - } -} - -struct AnimatedMovie { - var scaleRatio: Double = 1 - var rotationAngle = Angle.zero - var verticalOffset: Double = 0 -} -``` - -在这个例子中,我们创建了一个 `AnimatedMovie` 结构,它包含了动画所需的所有属性。然后,我们在 `ContentView` 视图中创建了一个 `KeyframeAnimator`,该修饰符接受一个观测值 `animationTrigger`,用于触发动画。在 `content` 闭包中,我们使用 `Image` 视图创建了一个电影海报,并根据 `AnimatedMovie` 结构中的值对其进行配置。在 `keyframes` 闭包中,我们为每个属性定义了一系列的关键帧轨道。例如,我们为 `scaleRatio` 属性定义了三个关键帧,分别使用 `LinearKeyframe` 和 `SpringKeyframe` 进行插值。我们还为 `rotationAngle` 和 `verticalOffset` 属性定义了两个关键帧轨道,分别使用 `CubicKeyframe` 和 `MoveKeyframe` 进行插值。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/Matched Geometry Effect(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/Matched Geometry Effect(ap).md" deleted file mode 100644 index db89a2d09..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/Matched Geometry Effect(ap).md" +++ /dev/null @@ -1,455 +0,0 @@ - -## 位置变化 - -Matched Geometry Effect 是一种特殊的动画效果。当你有两个视图,并且你想在一个视图消失,另一个视图出现时,创建一个平滑的过渡动画,你就可以使用这个效果。你只需要给这两个视图添加同样的标识符和命名空间,然后当你删除一个视图并添加另一个视图时,就会自动创建一个动画,让一个视图看起来像是滑动到另一个视图的位置。 - -示例代码如下: - -```swift -struct ContentView: View { - @StateObject var viewModel = ViewModel() - @Namespace var musicSelectionNamespace - var body: some View { - VStack { - HStack { - ForEach(viewModel.topMusic) { item in - Button(action: { viewModel.selectTopMusic(item) }) { - ZStack { - Image(item.name) - .resizable() - .frame(width: 60, height: 60) - Text(item.name) - .fontDesign(.rounded) - .foregroundColor(.white) - .shadow(radius: 10) - } - } - .matchedGeometryEffect(id: item.id, in: musicSelectionNamespace) - } - } - .frame(minHeight: 150) - Spacer() - .frame(height: 250) - HStack { - ForEach(viewModel.bottomMusic) { item in - Button(action: { viewModel.selectBottomMusic(item) }) { - ZStack { - Image(item.name) - .resizable() - .frame(width: 90, height: 90) - Text(item.name) - .font(.title3) - .fontWeight(.bold) - .foregroundColor(.white) - .shadow(radius: 10) - } - } - .matchedGeometryEffect(id: item.id, in: musicSelectionNamespace) - } - } - .frame(minHeight: 150) - } - } -} -``` - -以上代码中,我们创建了一个 `ContentView` 视图,其中包含两个 `HStack` 视图,分别展示了 `viewModel` 中的 `topMusic` 和 `bottomMusic` 数组。我们为每个 `topMusic` 和 `bottomMusic` 元素创建了一个 `Button` 视图,当用户点击按钮时,会调用 `viewModel` 中的 `selectTopMusic` 和 `selectBottomMusic` 方法。我们使用 `matchedGeometryEffect` 修饰符为每个 `Button` 视图添加了一个标识符,这样当用户点击按钮时,就会自动创建一个动画,让一个视图看起来像是滑动到另一个视图的位置。 - -## 大小变化 - -Matched Geometry Effect 在大小和位置上都可以进行动画过渡,这样可以让你创建更加复杂的动画效果。 - -以下是一个视图大小切换的示例: - -```swift -struct ContentView: View { - @State var isExpanded: Bool = false - - private var albumId = "Album" - - @Namespace var expansionAnimation - - var body: some View { - VStack { - albumView(isExpanded: isExpanded) - } - .padding() - .onTapGesture { - withAnimation { - isExpanded.toggle() - } - } - } - - @ViewBuilder - func albumView(isExpanded: Bool) -> some View { - let imageSize = isExpanded ? CGSize(width: 300, height: 450) : CGSize(width: 100, height: 150) - Image(isExpanded ? "evermore" : "fearless") - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: imageSize.width, height: imageSize.height) - .clipped() - .matchedGeometryEffect(id: albumId, in: expansionAnimation) - .overlay { - Text("Taylor Swift") - .font(isExpanded ? .largeTitle : .headline) - .fontDesign(.monospaced) - .fontDesign(.rounded) - .foregroundStyle(.white) - } - } -} -``` - - -## 内容位置变化 - -内容位置变化的动画效果。以下是一个内容位置变化的示例: - -```swift -struct ContentView: View { - @State var show = false - @Namespace var placeHolder - @State var albumCoverSize: CGSize = .zero - @State var songListSize: CGSize = .zero - var body: some View { - ZStack { - VStack { - Text("Taylor Swift,1989年12月13日出生于美国宾夕法尼亚州,美国乡村音乐、流行音乐女歌手、词曲创作人、演员、慈善家。") - .font(.title) - .fontDesign(.monospaced) - .fontDesign(.rounded) - .padding(20) - Spacer() - } - Color.clear - // AlbumCover placeholder - .overlay(alignment: .bottom) { - Color.clear // AlbumCoverView().opacity(0.01) - .frame(height: albumCoverSize.height) - .matchedGeometryEffect(id: "bottom", in: placeHolder, anchor: .bottom, isSource: true) - .matchedGeometryEffect(id: "top", in: placeHolder, anchor: .top, isSource: true) - } - .overlay( - AlbumCoverView() - .sizeInfo($albumCoverSize) - .matchedGeometryEffect(id: "bottom", in: placeHolder, anchor: show ? .bottom : .top, isSource: false) - ) - .overlay( - SongListView() - .matchedGeometryEffect(id: "top", in: placeHolder, anchor: show ? .bottom : .top, isSource: false) - ) - .animation(.default, value: show) - .ignoresSafeArea() - .overlayButton(show: $show) - } - } -} - -struct AlbumCoverView: View { - var body: some View { - Image("evermore") - .resizable() - .aspectRatio(contentMode: .fill) - } -} - -struct SongListView: View { - var body: some View { - List { - Text("Fearless") - Text("Speak Now") - Text("Red") - // ... - } - } -} - -extension View { - func overlayButton(show: Binding) -> some View { - self.overlay( - Button(action: { - withAnimation { - show.wrappedValue.toggle() - } - }) { - Image(systemName: "arrow.up.arrow.down.square") - .font(.largeTitle) - .padding() - .background(Color.white.opacity(0.75)) - .clipShape(Circle()) - } - .padding() - , alignment: .topTrailing - ) - } - - func sizeInfo(_ size: Binding) -> some View { - self.background( - GeometryReader { geometry in - Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size) - } - ) - .onPreferenceChange(SizePreferenceKey.self) { size.wrappedValue = $0 } - } -} - -struct SizePreferenceKey: PreferenceKey { - static var defaultValue: CGSize = .zero - static func reduce(value: inout CGSize, nextValue: () -> CGSize) { - value = nextValue() - } -} -``` - -我们使用 `matchedGeometryEffect` 修饰符为 `AlbumCoverView` 和 `SongListView` 添加了一个标识符,这样当用户点击按钮时,就会自动创建一个动画,让 `AlbumCoverView` 和 `SongListView` 看起来像是从一个位置切换到另一个位置。 - - -## 点击显示详细信息 - -点击显示详细信息的动画效果。 - -```swift -struct ContentView: View { - @Namespace var animation - @State var showDetail = false - - var body: some View { - ZStack { - if (!showDetail) { - VStack { - Text("Taylor Swift") - .matchedGeometryEffect(id: "artist", in: animation) - .font(.largeTitle.bold()) - .foregroundColor(Color.white) - - Text("美国歌手") - .matchedGeometryEffect(id: "description", in: animation) - .font(.title3.bold()) - .foregroundColor(Color.white) - - - } - .padding(30) - .background( - Rectangle().fill(.black.gradient) - .matchedGeometryEffect(id: "background", in: animation) - - ) - } else { - SingerView(animation: animation) - - } - } - .onTapGesture { - withAnimation { - showDetail.toggle() - } - } - } -} - -struct SingerView: View { - var animation: Namespace.ID - - var body: some View { - VStack{ - Text("Taylor Swift") - .matchedGeometryEffect(id: "artist", in: animation) - .font(.largeTitle.bold()) - .foregroundColor(Color.white) - - Text("美国歌手") - .matchedGeometryEffect(id: "description", in: animation) - .font(.title3.bold()) - .foregroundColor(Color.white) - - Spacer() - .frame(height: 30) - Text("泰勒·阿利森·斯威夫特(Taylor Swift),1989年12月13日出生于美国宾夕法尼亚州,美国乡村音乐、流行音乐女歌手、词曲创作人、演员、慈善家。") - .font(.subheadline.bold()) - .foregroundColor(Color.white) - - Spacer() - .frame(height: 30) - Image("evermore") - .resizable() - .scaledToFit() - .clipShape(.rect(cornerSize: CGSize(width: 16, height: 16))) - - Text("Evermore 是 Taylor Swift 的最新专辑,这是她在 2020 年的第二张专辑,也是她的第九张录音室专辑。") - .font(.subheadline.bold()) - .foregroundColor(Color.white) - - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .padding(.all, 20) - .background( - Rectangle().fill(.black.gradient) - .matchedGeometryEffect(id: "background", in: animation) - .ignoresSafeArea(.all) - ) - } -} -``` - - -## 导航动画 - -以下是一个导航动画的示例: - -```swift -struct ContentView: View { - @Namespace var animation - @State var selectedManga: String? = nil - - var body: some View { - ZStack { - if (selectedManga == nil) { - MangaListView(animation: animation, selectedManga: $selectedManga) - - } else { - MangaDetailView(selectedManga: $selectedManga, animation: animation) - } - } - - } -} - -struct MangaDetailView: View { - @Binding var selectedManga: String? - var animation: Namespace.ID - - var body: some View { - VStack { - Text( "\(selectedManga ?? "")") - .matchedGeometryEffect(id: "mangaTitle", in: animation) - .font(.title3.bold()) - .foregroundColor(Color.black) - - Spacer() - .frame(height: 50) - - Button(action: { - withAnimation { - selectedManga = nil - } - }, label: { - Text( "返回") - .font(.title3.bold()) - .foregroundColor(Color.black) - }) - .foregroundColor(Color.red) - .padding(.all, 8) - .background( - RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) - .fill(Color.white.gradient) - ) - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .padding(.all, 20) - .background( - Color(UIColor.systemTeal) - .matchedGeometryEffect(id: "background", in: animation) - .ignoresSafeArea(.all) - ) - } -} - - -struct MangaListView: View { - var animation: Namespace.ID - @Binding var selectedManga: String? - - var body: some View { - VStack { - Button(action: { - withAnimation { - selectedManga = "海贼王" - } - }, label: { - Text( "海贼王") - .matchedGeometryEffect(id: "mangaTitle", in: animation) - .font(.title3.bold()) - .foregroundColor(Color.black) - }) - .foregroundColor(Color.black) - .padding(.all, 8) - .background( - RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) - .fill(Color.teal) - ) - - Button(action: { - withAnimation { - selectedManga = "火影忍者" - } - }, label: { - Text( "火影忍者") - .font(.title3.bold()) - .foregroundColor(Color.black) - }) - .foregroundColor(Color.black) - .padding(.all, 8) - .background( - RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) - .fill(Color.mint) - .matchedGeometryEffect(id: "background", in: animation) - ) - - Button(action: { - withAnimation { - selectedManga = "进击的巨人" - } - }, label: { - Text( "进击的巨人") - .font(.title3.bold()) - .foregroundColor(Color.black) - }) - .foregroundColor(Color.black) - .padding(.all, 8) - .background( - RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) - .fill(Color.orange) - ) - - Button(action: { - withAnimation { - selectedManga = "鬼灭之刃" - } - }, label: { - Text( "鬼灭之刃") - .font(.title3.bold()) - .foregroundColor(Color.black) - }) - .foregroundColor(Color.black) - .padding(.all, 8) - .background( - RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) - .fill(Color.purple) - ) - - Button(action: { - withAnimation { - selectedManga = "我的英雄学院" - } - }, label: { - Text( "我的英雄学院") - .font(.title3.bold()) - .foregroundColor(Color.black) - }) - .foregroundColor(Color.black) - .padding(.all, 8) - .background( - RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) - .fill(Color.green) - ) - } - } -} - -``` - -## geometryGroup - -`.geometryGroup()` 主要用于处理一组视图动画变化时不协调的问题。如果你有一组视图,它们的位置和大小会随着动画变化,你可以使用 `.geometryGroup()` 修饰符来确保它们的位置和大小保持一致。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/PhaseAnimator(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/PhaseAnimator(ap).md" deleted file mode 100644 index 2abe7bbe4..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/PhaseAnimator(ap).md" +++ /dev/null @@ -1,55 +0,0 @@ - -PhaseAnimator - -以下代码示例演示了如何使用 `PhaseAnimator` 视图修饰符创建一个动画,该动画通过循环遍历所有动画步骤来连续运行。在这个例子中,我们使用 `PhaseAnimator` 来创建一个简单的动画,该动画通过循环遍历所有动画步骤来连续运行。当观测值发生变化时,动画会触发一次。 - -```swift -enum AlbumAnimationPhase: String, CaseIterable, Comparable { - case evermore, fearless, folklore, lover, midnights, red, speaknow - - static func < (lhs: AlbumAnimationPhase, rhs: AlbumAnimationPhase) -> Bool { - lhs.rawValue < rhs.rawValue - } -} - -struct ContentView: View { - @State var animate: Bool = false - - var body: some View { - ScrollView { - PhaseAnimator( - AlbumAnimationPhase.allCases, - trigger: animate, - content: { phase in - VStack { - ForEach(AlbumAnimationPhase.allCases, id: \.self) { album in - if phase >= album { - VStack { - Image(album.rawValue) - .resizable() - .frame(width: 100, height: 100) - Text(album.rawValue.capitalized) - .font(.title) - } - .transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing))) - } - } - } - .padding() - }, animation: { phase in - .spring(duration: 0.5) - } - ) - } // end ScrollView - Button(action: { - animate.toggle() - }, label: { - Text("开始") - .font(.largeTitle) - .bold() - }) - } -} -``` - -在上面的代码中,我们首先定义了一个枚举类型 `AlbumAnimationPhase`,用于表示专辑的不同阶段。然后,我们在 `ContentView` 视图中创建了一个 `PhaseAnimator` 视图修饰符,该修饰符接受一个观测值 `trigger`,用于触发动画。在 `content` 闭包中,我们遍历所有专辑,并根据当前阶段 `phase` 来决定是否显示专辑。在 `animation` 闭包中,我们使用 `.spring(duration: 0.5)` 创建了一个弹簧动画效果。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/SwiftUI\345\212\250\347\224\273(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/SwiftUI\345\212\250\347\224\273(ap).md" deleted file mode 100644 index 4a918aaa8..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/SwiftUI\345\212\250\347\224\273(ap).md" +++ /dev/null @@ -1,304 +0,0 @@ -SwiftUI 里实现动画的方式包括有 .animation 隐式动画、withAnimation 和 withTransaction 显示动画、matchedGeometryEffect Hero 动画和 TimelineView 等。 - -示例代码如下: - -```swift -struct PlayAnimation: View { - @State private var isChange = false - private var anis:[String: Animation] = [ - "p1": .default, - "p2": .linear(duration: 1), - "p3": .interpolatingSpring(stiffness: 5, damping: 3), - "p4": .easeInOut(duration: 1), - "p5": .easeIn(duration: 1), - "p6": .easeOut(duration: 1), - "p7": .interactiveSpring(response: 3, dampingFraction: 2, blendDuration: 1), - "p8": .spring(), - "p9": .default.repeatCount(3) - ] - @State private var selection = 1 - - var body: some View { - // animation 隐式动画和 withAnimation 显示动画 - Text(isChange ? "另一种状态" : "一种状态") - .font(.headline) - .padding() - .animation(.easeInOut, value: isChange) // 受限的隐式动画,只绑定某个值。 - .onTapGesture { - // 使用 withAnimation 就是显式动画,效果等同 withTransaction(Transaction(animation: .default)) - withAnimation { - isChange.toggle() - } - - // 设置 Transaction。和隐式动画共存时,优先执行 withAnimation 或 Transaction。 - var t = Transaction(animation: .linear(duration: 2)) - t.disablesAnimations = true // 用来禁用隐式动画 - withTransaction(t) { - isChange.toggle() - } - } // end onHover - - LazyVGrid(columns: [GridItem(.adaptive(minimum: isChange ? 60 : 30), spacing: 60)]) { - ForEach(Array(anis.keys), id: \.self) { s in - Image(s) - .resizable() - .scaledToFit() - .animation(anis[s], value: isChange) - .scaleEffect() - } - } - .padding() - Button { - isChange.toggle() - } label: { - Image(systemName: isChange ? "pause.fill" : "play.fill") - .renderingMode(.original) - } - - // matchedGeometryEffect 的使用 - VStack { - Text("后台") - .font(.headline) - placeStayView - Text("前台") - .font(.headline) - placeShowView - } - .padding(50) - - // 通过使用相同 matchedGeometryEffect 的 id,绑定两个元素变化。 - HStack { - if isChange { - Rectangle() - .fill(.pink) - .matchedGeometryEffect(id: "g1", in: mgeStore) - .frame(width: 100, height: 100) - } - Spacer() - Button("转换") { - withAnimation(.linear(duration: 2.0)) { - isChange.toggle() - } - } - Spacer() - if !isChange { - Circle() - .fill(.orange) - .matchedGeometryEffect(id: "g1", in: mgeStore) - .frame(width: 70, height: 70) - } - HStack { - Image("p1") - .resizable() - .scaledToFit() - .frame(width: 50, height: 50) - if !isChange { - Image("p19") - .resizable() - .scaledToFit() - .frame(width: 50, height: 50) - .matchedGeometryEffect(id: "g1", in: mgeStore) - } - Image("p1") - .resizable() - .scaledToFit() - .frame(width: 50, height: 50) - } - } - .padding() - - // 使用 isSource,作为移动到相同 matchedGeometryEffect id 的方法。 - HStack { - Image("p19") - .resizable() - .scaledToFit() - .frame(width: isChange ? 100 : 50, height: isChange ? 100 : 50) - .matchedGeometryEffect(id: isChange ? "g2" : "", in: mgeStore, isSource: false) - - Image("p19") - .resizable() - .scaledToFit() - .frame(width: 100, height: 100) - .matchedGeometryEffect(id: "g2", in: mgeStore) - .opacity(0) - } - - - - // 点击跟随的效果 - HStack { - ForEach(Array(1...4), id: \.self) { i in - Image("p\(i)") - .resizable() - .scaledToFit() - .frame(width: i == selection ? 200 : 50) - .matchedGeometryEffect(id: "h\(i)", in: mgeStore) - .onTapGesture { - withAnimation { - selection = i - } - } - .shadow(color: .black, radius: 3, x: 2, y: 3) - } - } - .background( - RoundedRectangle(cornerRadius: 8).fill(.pink) - .matchedGeometryEffect(id: "h\(selection)", in: mgeStore, isSource: false) - ) - - // matchedGeometryEffect 还可以应用到 List 中,通过 Array enumerated 获得 index 作为 matchedGeometryEffect 的 id。右侧固定按钮可以直接让对应 id 的视图滚动到固定按钮的位置 - - - // TimelineView - TimelineView(.periodic(from: .now, by: 1)) { t in - Text("\(t.date)") - HStack(spacing: 20) { - let e = "p\(Int.random(in: 1...30))" - Image(e) - .resizable() - .scaledToFit() - .frame(height: 40) - .animation(.default.repeatCount(3), value: e) - - TimelineSubView(date: t.date) // 需要传入 timeline 的时间给子视图才能够起作用。 - - } - .padding() - } - - // matchedGeometryEffect - - /// TimelineScheduler 的使用,TimelineScheduler 有以下类型 - /// .animation:制定更新的频率,可以控制暂停 - /// .everyMinute:每分钟更新一次 - /// .explicit:所有要更新的放到一个数组里 - /// .periodic:设置开始时间和更新频率 - /// 也可以自定义 TimelineScheduler - TimelineView(.everySecond) { t in - let e = "p\(Int.random(in: 1...30))" - Image(e) - .resizable() - .scaledToFit() - .frame(height: 40) - } - - // 自定义的 TimelineScheduler - TimelineView(.everyLoop(timeOffsets: [0.2, 0.7, 1, 0.5, 2])) { t in - TimelineSubView(date: t.date) - } - } - - // MARK: - TimelineSubView - struct TimelineSubView: View { - let date : Date - @State private var s = "let's go" - // 顺序从数组中取值,取完再重头开始 - @State private var idx: Int = 1 - func advanceIndex(count: Int) { - idx = (idx + 1) % count - if idx == 0 { idx = 1 } - } - - var body: some View { - HStack(spacing: 20) { - Image("p\(idx)") - .resizable() - .scaledToFit() - .frame(height: 40) - .animation(.easeIn(duration: 1), value: date) - .onChange(of: date) { newValue in - advanceIndex(count: 30) - s = "\(date.hour):\(date.minute):\(date.second)" - } - .onAppear { - advanceIndex(count: 30) - } - - Text(s) - } - } - } - - // MARK: - 用 matchedGeometryEffect 做动画 - /// matchedGeometryEffect 可以无缝的将一个图像变成另外一个图像。 - @State private var placeStayItems = ["p1", "p2", "p3", "p4"] - @State private var placeShowItems: [String] = [] - - @Namespace private var mgeStore - - private var placeStayView: some View { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 30), spacing: 10)]) { - ForEach(placeStayItems, id: \.self) { s in - Image(s) - .resizable() - .scaledToFit() - .matchedGeometryEffect(id: s, in: mgeStore) - .onTapGesture { - withAnimation { - placeStayItems.removeAll { $0 == s } - placeShowItems.append(s) - } - } - .shadow(color: .black, radius: 2, x: 2, y: 4) - } // end ForEach - } // end LazyVGrid - } // private var placeStayView - - private var placeShowView: some View { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 150), spacing: 10)]) { - ForEach(placeShowItems, id: \.self) { s in - Image(s) - .resizable() - .scaledToFit() - .matchedGeometryEffect(id: s, in: mgeStore) - .onTapGesture { - withAnimation { - placeShowItems.removeAll { $0 == s } - placeStayItems.append(s) - } - } - .shadow(color: .black, radius: 2, x: 0, y: 2) - .shadow(color: .white, radius: 5, x: 0, y: 2) - } // end ForEach - } // end LazyVGrid - } // end private var placeShowView - -} // end struct PlayAnimation - -// MARK: - 扩展 TimelineSchedule -extension TimelineSchedule where Self == PeriodicTimelineSchedule { - static var everySecond: PeriodicTimelineSchedule { - get { - .init(from: .now, by: 1) - } - } -} - -// MARK: - 自定义一个 TimelineSchedule -// timeOffsets 用完,就会再重头重新再来一遍 -struct PCLoopTimelineSchedule: TimelineSchedule { - let timeOffsets: [TimeInterval] - - func entries(from startDate: Date, mode: TimelineScheduleMode) -> Entries { - Entries(last: startDate, offsets: timeOffsets) - } - - struct Entries: Sequence, IteratorProtocol { - var last: Date - let offsets: [TimeInterval] - var idx: Int = -1 - mutating func next() -> Date? { - idx = (idx + 1) % offsets.count - last = last.addingTimeInterval(offsets[idx]) - return last - } - } // end Struct Entries -} - -// 为自定义的 PCLoopTimelineSchedule 做一个 TimelineSchedule 的扩展函数,方便使用 -extension TimelineSchedule where Self == PCLoopTimelineSchedule { - static func everyLoop(timeOffsets: [TimeInterval]) -> PCLoopTimelineSchedule { - .init(timeOffsets: timeOffsets) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/Transaction(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/Transaction(ap).md" deleted file mode 100644 index 28eaf9743..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/Transaction(ap).md" +++ /dev/null @@ -1,357 +0,0 @@ - -## Transaction 使用指南 - -这段内容主要介绍了 SwiftUI 中的 `Transaction` 和 `withTransaction`。`Transaction` 是 SwiftUI 中用于控制动画的一种方式,它可以用来定义动画的详细参数,如动画类型、持续时间等。`withTransaction` 是一个函数,它接受一个 `Transaction` 实例和一个闭包作为参数,闭包中的代码将在这个 `Transaction` 的上下文中执行。 - -以下是一个使用 `Transaction` 和 `withTransaction` 的代码示例: - -```swift -struct ContentView: View { - @State private var scale: CGFloat = 1.0 - - var body: some View { - VStack { - Text("电影标题") - .font(.title) - .padding() - Image("evermore") - .resizable() - .scaledToFit() - .scaleEffect(scale) - } - .onAppear { - let transaction = Transaction(animation: .easeInOut(duration: 1)) - withTransaction(transaction) { - scale = 1.5 - } - } - } -} -``` - -在这个示例中,我们创建了一个 `MovieView`,它包含一个电影标题和一个电影海报。当视图出现时,我们创建了一个 `Transaction`,并设置了动画类型为 `easeInOut`,持续时间为 1 秒。然后我们在 `withTransaction` 的闭包中改变 `scale` 的值,这样海报的大小就会以 `easeInOut` 的方式在 1 秒内放大到 1.5 倍。 - -## 使用 `Transaction` 和 `withTransaction` - -SwiftUI 中 `Transaction` 的 `disablesAnimations` 和 `isContinuous` 属性,以及 `transaction(_:)` 方法怎么使用? - -`disablesAnimations` 属性可以用来禁止动画,`isContinuous` 属性可以用来标识一个连续的交互(例如拖动)。`transaction(_:)` 方法可以用来创建一个新的 `Transaction` 并在其闭包中设置动画参数。 - -以下是一个使用这些特性的代码示例: - -```swift -struct ContentView: View { - @State var size: CGFloat = 100 - @GestureState var dragSize: CGSize = .zero - - var body: some View { - VStack { - Image("fearless") - .resizable() - .scaledToFit() - .frame(width: size, height: size) // 使用 size 控制尺寸,而非位置 - .animation(.spring(), value: size) // 使用弹簧动画 - .transaction { - if $0.isContinuous { - $0.animation = nil // 拖动时,不设置动画 - } else { - $0.animation = .spring() // 使用弹簧动画 - } - } - .gesture( - DragGesture() - .updating($dragSize, body: { current, state, transaction in - state = .init(width: current.translation.width, height: current.translation.height) - transaction.isContinuous = true // 拖动时,设置标识 - }) - ) - - Stepper("尺寸: \(size)", value: $size, in: 50...200) // 使用 Stepper 替代 Slider - Button("开始动画") { - var transaction = Transaction() - if size < 150 { transaction.disablesAnimations = true } - withTransaction(transaction) { - size = 50 - } - } - } - } -} -``` - -在这个示例中,当 `size` 小于 150 时,我们禁用动画。通过 `.isContinuous` 属性,我们可以标识一个连续的交互(例如拖动)。在这个示例中,当拖动时,我们禁用动画。通过 `transaction(_:)` 方法,我们可以创建一个新的 `Transaction` 并在其中设置动画参数。 - -## 用于视图组件 - -大部分 SwiftUI 视图组件都有 `transaction(_:)` 方法,可以用来设置动画参数。比如 NavigationStack, Sheet, Alert 等。 - -`Transaction` 也可以用于 `Binding` 和 `FetchRequest`。 - -看下面的例子: - -```swift -struct ContentView: View { - @State var size: CGFloat = 100 - @State var isBold: Bool = false - let animation: Animation? = .spring - - var sizeBinding: Binding { - let transaction = Transaction(animation: animation) - return $size.transaction(transaction) - } - - var isBoldBinding: Binding { - let transaction = Transaction(animation: animation) - return $isBold.transaction(transaction) - } - - var body: some View { - VStack { - Image(systemName: "film") - .resizable() - .scaledToFit() - .frame(width: size, height: size) // 使用 size 控制尺寸,而非位置 - .font(.system(size: size, weight: isBold ? .bold : .regular)) // 使用 isBold 控制粗细 - Stepper("尺寸: \(size)", value: sizeBinding, in: 50...200) - Toggle("粗细", isOn: isBoldBinding) - } - .padding() - } -} -``` - -## 传播行为 - -`Transaction` 可以用于控制动画的传播行为。在 SwiftUI 中,动画可以在视图层次结构中传播,这意味着一个视图的动画效果可能会影响到其子视图。`Transaction` 可以用来控制动画的传播行为,例如禁用动画、设置动画类型等。 - -以下是一个使用 `Transaction` 控制动画传播行为的代码示例: - -```swift -enum BookStatus { - case small, medium, large, extraLarge -} - -extension View { - @ViewBuilder func debugAnimation() -> some View { - transaction { - debugPrint($0.animation ?? "") - } - } -} - -struct ContentView: View { - @State var status: BookStatus = .small - - var animation: Animation? { - switch status { - case .small: - return .linear - case .medium: - return .easeIn - case .large: - return .easeOut - case .extraLarge: - return .spring() - } - } - - var size: CGFloat { - switch status { - case .small: - return 100 - case .medium: - return 200 - case .large: - return 300 - case .extraLarge: - return 400 - } - } - - var body: some View { - VStack { - Image(systemName: "book") - .resizable() - .scaledToFit() - .frame(width: size, height: size) - .debugAnimation() // 查看动画变化信息 - Button("改变状态") { - var transaction = Transaction(animation: animation) - withTransaction(transaction) { - switch self.status { - case .small: - self.status = .medium - case .medium: - self.status = .large - case .large: - self.status = .extraLarge - case .extraLarge: - self.status = .small - } - } - } - } - } -} -``` - -这个示例中,我们创建了一个 `BookView`,它包含一个书籍图标。我们通过 `BookStatus` 枚举来控制书籍的大小,通过 `animation` 计算属性来根据状态返回不同的动画类型。在 `withTransaction` 中,我们根据状态创建一个新的 `Transaction`,并在其中设置动画类型。通过 `debugAnimation` 修饰符,我们可以查看动画的变化信息。 - -## TransactionKey - -TransactionKey 是一种在 SwiftUI 的视图更新过程中传递额外信息的机制,它可以让你在不同的视图和视图更新之间共享数据。 - -```swift -struct ContentView: View { - @State private var store = MovieStore() - var body: some View { - VStack { - Image("evermore") - .resizable() - .scaledToFit() - .frame(width: 300, height: 300) - .saturation(store.isPlaying ? 1 : 0) // 滤镜变化 - .transaction { - $0.animation = $0[StatusKey.self].animation - } - - PlayView(store: store) - PauseView(store: store) - } - } -} - -struct PlayView: View { - let store: MovieStore - var body: some View { - Button("播放") { - withTransaction(\.status, .playing) { - store.isPlaying.toggle() - } - } - } -} - -struct PauseView: View { - let store: MovieStore - var body: some View { - Button("暂停") { - withTransaction(\.status, .paused) { - store.isPlaying.toggle() - } - } - } -} - -@Observable -class MovieStore { - var isPlaying = false -} - -enum MovieStatus { - case playing - case paused - case stopped - - var animation: Animation? { - switch self { - case .playing: - Animation.linear(duration: 2) - case .paused: - nil - case .stopped: - Animation.easeInOut(duration: 1) - } - } -} - -struct StatusKey: TransactionKey { - static var defaultValue: MovieStatus = .stopped -} - -extension Transaction { - var status: MovieStatus { - get { self[StatusKey.self] } - set { self[StatusKey.self] = newValue } - } -} -``` - -以上代码中,我们创建了一个 `MovieStore` 类,用于存储电影播放状态。我们通过 `PlayView` 和 `PauseView` 分别创建了播放和暂停按钮,点击按钮时,我们通过 `withTransaction` 函数改变了 `MovieStore` 的 `isPlaying` 属性,并根据状态设置了动画类型。在 `ContentView` 中,我们通过 `transaction` 修饰符设置了动画类型为 `MovieStatus` 中的动画类型。 - -## AnyTransition - -`AnyTransition` 是一个用于创建自定义过渡效果的类型,它可以让你定义视图之间的过渡动画。你可以使用 `AnyTransition` 的 `modifier` 方法将自定义过渡效果应用到视图上。 - -```swift -struct ContentView: View { - - @StateObject var musicViewModel = MusicViewModel() - - var body: some View { - VStack { - ForEach(musicViewModel.musicNames, id: \.description) { musicName in - if musicName == musicViewModel.currentMusic { - Image(musicName) - .resizable() - .frame(width: 250, height: 250) - .ignoresSafeArea() - .transition(.glitch.combined(with: .opacity)) - } - } - - Button("Next Music") { - musicViewModel.selectNextMusic() - } - .buttonStyle(.borderedProminent) - .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(10) - } - } -} - -struct MyTransition: ViewModifier { - let active: Bool - - func body(content: Content) -> some View { - content - .rotationEffect(active ? .degrees(Double.random(in: -10...10)) : .zero) - .offset(x: active ? CGFloat.random(in: -10...10) : 0, y: active ? CGFloat.random(in: -10...10) : 0) - } -} - -extension AnyTransition { - static var glitch: AnyTransition { - AnyTransition.modifier( - active: MyTransition(active: true), - identity: MyTransition(active: false) - ) - } -} - -class MusicViewModel: ObservableObject { - @Published var currentMusic = "" - - let musicNames = ["fearless", "evermore", "red", "speaknow", "lover"] - - init() { - currentMusic = musicNames.first ?? "fearless" - } - - func selectNextMusic() { - guard let currentIndex = musicNames.firstIndex(of: currentMusic) else { - return - } - - let nextIndex = currentIndex + 1 < musicNames.count ? currentIndex + 1 : 0 - - withAnimation(.easeInOut(duration: 2)) { - currentMusic = musicNames[nextIndex] - } - } -} -``` - -以上代码中,我们创建了一个 `MusicViewModel` 类,用于存储音乐播放状态。我们通过 `MyTransition` 自定义了一个过渡效果,通过 `AnyTransition` 的 `modifier` 方法将自定义过渡效果应用到视图上。在 `ContentView` 中,我们通过 `transition` 修饰符设置了过渡效果为 `glitch`,并在点击按钮时切换音乐。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/animation\344\277\256\351\245\260\347\254\246(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/animation\344\277\256\351\245\260\347\254\246(ap).md" deleted file mode 100644 index c6cee2347..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/animation\344\277\256\351\245\260\347\254\246(ap).md" +++ /dev/null @@ -1,298 +0,0 @@ - - - -## 基本用法 - -在 SwiftUI 中,创建一个动画需要以下三个组成部分: - -- 一个时间曲线函数 -- 一个声明将状态(或特定的依赖项)与该时间曲线函数关联起来 -- 一个依赖于该状态(或特定的依赖项)的可动画组件 - -动画的接口定义为 `Animation(timingFunction:property:duration:delay)` - -- `timingFunction` 是时间曲线函数,可以是线性、缓动、弹簧等 -- `property` 是动画属性,可以是颜色、大小、位置等 -- `duration` 是动画持续时间 -- `delay` 是动画延迟时间 - -三种写法 - -- `withAnimation(_:_:)` 全局应用 -- `animation(_:value:)` 应用于 View -- `animation(_:)` 应用于绑定的变量 - -第一种 - -```swift -withAnimation(.easeInOut(duration: 1.5).delay(1.0)) { - myProperty = newValue -} -``` - -第二种 - -```swift -View().animation(.easeInOut(duration: 1.5).delay(1.0), value: myProperty) -``` - -第三种 - -```swift -struct ContentView: View { - @State private var scale: CGFloat = 1.0 - var body: some View { - PosterView(scale: $scale.animation(.linear(duration: 1))) - } -} - -struct PosterView: View { - @Binding var scale: CGFloat - var body: some View { - Image("evermore") - .resizable() - .scaledToFit() - .scaleEffect(scale) - .onAppear { - scale = 1.5 - } - } -} -``` - -在这个示例中,我们创建了一个 `MovieView`,它有一个状态变量 `scale`。当 `scale` 的值改变时,`PosterView` 中的海报图片会以线性动画的方式进行缩放。当 `PosterView` 出现时,`scale` 的值会改变为 1.5,因此海报图片会以线性动画的方式放大到 1.5 倍。 - -在 SwiftUI 中,我们也可以创建一个自定义的 `AnimatableModifier` 来实现对图文卡片大小的动画处理。 - -```swift -struct ContentView: View { - @State private var isSmall = false - var body: some View { - VStack { - Image("evermore") - .resizable() - .aspectRatio(contentMode: .fit) - .clipShape(.rect(cornerSize: CGSize(width: 16, height: 16))) - Text("电影标题") - .font(.title) - .fontWeight(.bold) - } - .animatableCard(size: isSmall ? CGSize(width: 200, height: 300) : CGSize(width: 400, height: 600)) - .onTapGesture { - withAnimation(.easeInOut(duration: 1)){ - isSmall.toggle() - } - } - } -} - -struct AnimatableCardModifier: AnimatableModifier { - var size: CGSize - var color: Color = .white - - var animatableData: CGSize.AnimatableData { - get { CGSize.AnimatableData(size.width, size.height) } - set { size = CGSize(width: newValue.first, height: newValue.second) } - } - - func body(content: Content) -> some View { - content - .frame(width: size.width, height: size.height) - .background(color) - .cornerRadius(10) - } -} - -extension View { - func animatableCard(size: CGSize, - color: Color = .white) -> some View { - self.modifier(AnimatableCardModifier(size: size, - color: color)) - } -} -``` - -SwiftUI 内置了许多动画过渡函数,主要分为四类: - -- 时间曲线动画函数 -- 弹簧动画函数 -- 高阶动画函数 -- 自定义动画函数 - - -### 时间曲线动画函数 - -时间曲线函数决定了动画的速度如何随时间变化,这对于动画的自然感觉非常重要。 - -SwiftUI 提供了以下几种预设的时间曲线函数: - -- `linear`:线性动画,动画速度始终保持不变。 -- `easeIn`:动画开始时速度较慢,然后逐渐加速。 -- `easeOut`:动画开始时速度较快,然后逐渐减速。 -- `easeInOut`:动画开始和结束时速度较慢,中间阶段速度较快。 - -除此之外,SwiftUI 还提供了 `timingCurve` 函数,可以通过二次曲线或 Bézier 曲线来自定义插值函数,实现更复杂的动画效果。 - -以下是代码示例: - -```swift -struct ContentView: View { - @State private var scale: CGFloat = 1.0 - - var body: some View { - VStack { - Text("电影标题") - .font(.title) - .padding() - Image("evermore") - .resizable() - .scaledToFit() - .scaleEffect(scale) - } - .onAppear { - withAnimation(.easeInOut(duration: 1.0)) { - scale = 1.5 - } - } - } -} -``` - -在这个示例中,我们创建了一个 `MovieView`,它包含一个电影标题和一个电影海报。当视图出现时,海报的大小会以 `easeInOut` 的方式在 1 秒内放大到 1.5 倍。 - -### 弹簧动画函数 - -弹簧动画函数可以模拟物理世界中的弹簧运动,使动画看起来更加自然和生动。 - -SwiftUI 提供了以下几种预设的弹簧动画函数: - -- `smooth`:平滑的弹簧动画,动画速度逐渐减慢,直到停止。 -- `snappy`:快速的弹簧动画,动画速度快速减慢,然后停止。 -- `bouncy`:弹跳的弹簧动画,动画在结束时会有一些弹跳效果。 - -除此之外,SwiftUI 还提供了 `spring` 函数,可以自定义弹簧动画的持续时间、弹跳度和混合持续时间,实现更复杂的弹簧动画效果。 - -以下是代码示例: - -```swift -struct ContentView: View { - @State private var scale: CGFloat = 1.0 - - var body: some View { - VStack { - Text("电影标题") - .font(.title) - .padding() - Image("evermore") - .resizable() - .scaledToFit() - .scaleEffect(scale) - } - .onAppear { - withAnimation(.spring(response: 0.5, dampingFraction: 0.5, blendDuration: 1)) { - scale = 1.5 - } - } - } -} -``` - -在这个示例中,我们创建了一个 `MovieView`,它包含一个电影标题和一个电影海报。当视图出现时,海报的大小会以自定义的弹簧动画方式在 0.5 秒内放大到 1.5 倍。 - -### 高阶动画函数 - -高级动画函数可以在基础动画函数的基础上,添加延迟、重复、翻转和速度等功能,使动画效果更加丰富和复杂。 - -以下是这些函数的简单介绍: - -- `func delay(TimeInterval) -> Animation`:此函数可以使动画在指定的时间间隔后开始。 -- `func repeatCount(Int, autoreverses: Bool) -> Animation`:此函数可以使动画重复指定的次数。如果 `autoreverses` 参数为 `true`,则每次重复时动画都会翻转。 -- `func repeatForever(autoreverses: Bool) -> Animation`:此函数可以使动画无限次重复。如果 `autoreverses` 参数为 `true`,则每次重复时动画都会翻转。 -- `func speed(Double) -> Animation`:此函数可以调整动画的速度,使其比默认速度快或慢。 - -以下是代码示例: - -```swift -struct MovieView: View { - @State private var scale: CGFloat = 1.0 - - var body: some View { - VStack { - Text("电影标题") - .font(.title) - .padding() - Image("movie_poster") - .resizable() - .scaledToFit() - .scaleEffect(scale) - } - .onAppear { - withAnimation(Animation.easeInOut(duration: 1.0).delay(0.5).repeatCount(3, autoreverses: true).speed(2)) { - scale = 1.5 - } - } - } -} -``` - -在这个示例中,我们创建了一个 `MovieView`,它包含一个电影标题和一个电影海报。当视图出现时,海报的大小会以 `easeInOut` 的方式在 1 秒内放大到 1.5 倍,然后在 0.5 秒后开始,重复 3 次,每次重复都会翻转,速度是默认速度的 2 倍。 - -### 自定义动画函数 - -SwiftUI 可以通过实现 `CustomAnimation` 协议来完全自定义插值算法。 - -以下是一个简单的 `Linear` 动画函数的实现: - -```swift -struct ContentView: View { - @State private var scale: CGFloat = 1.0 - - var body: some View { - VStack { - Text("电影标题") - .font(.title) - .padding() - Image("evermore") - .resizable() - .scaledToFit() - .scaleEffect(scale) - .animation(.myLinear(duration: 1), value: scale) // use myLinear animation - } - .onAppear { - scale = 1.5 - } - } -} - - -struct MyLinearAnimation: CustomAnimation { - var duration: TimeInterval - - func animate(value: V, time: TimeInterval, context: inout AnimationContext) -> V? where V : VectorArithmetic { - if time <= duration { - value.scaled(by: time / duration) - } else { - nil - } - } - - func velocity( - value: V, time: TimeInterval, context: AnimationContext - ) -> V? { - value.scaled(by: 1.0 / duration) - } - - func shouldMerge(previous: Animation, value: V, time: TimeInterval, context: inout AnimationContext) -> Bool where V : VectorArithmetic { - true - } -} - -extension Animation { - public static func myLinear(duration: TimeInterval) -> Animation { // define function like linear - return Animation(MyLinearAnimation(duration: duration)) - } -} -``` - - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/contentTransition(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/contentTransition(ap).md" deleted file mode 100644 index 76c72890a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/contentTransition(ap).md" +++ /dev/null @@ -1,19 +0,0 @@ - -`.contentTransition(.numericText())` 修饰符用于在视图内容发生变化时,以数字动画的方式进行过渡。 - -```swift -struct ContentView: View { - @State private var filmNumber: Int = 0 - - var body: some View { - VStack(spacing: 20) { - Text("\(filmNumber)") - .contentTransition(.numericText()) - .animation(.easeIn, value: filmNumber) - Stepper("电影数量", value: $filmNumber, in: 0...100) - } - .font(.largeTitle) - .foregroundColor(.indigo) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/\345\212\250\347\224\273-\344\276\213\345\255\220(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/\345\212\250\347\224\273-\344\276\213\345\255\220(ap).md" deleted file mode 100644 index 1255a0a13..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/\345\212\250\347\224\273-\344\276\213\345\255\220(ap).md" +++ /dev/null @@ -1,2 +0,0 @@ - -动画的例子有很多。准备中... 请期待。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/\345\270\203\345\261\200\345\212\250\347\224\273(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/\345\270\203\345\261\200\345\212\250\347\224\273(ap).md" deleted file mode 100644 index bcab90efd..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\212\250\347\224\273/\345\270\203\345\261\200\345\212\250\347\224\273(ap).md" +++ /dev/null @@ -1,31 +0,0 @@ -```swift -import SwiftUI - -struct AnimateLayout: View { - @State var changeLayout: Bool = true - @Namespace var namespace - - var body: some View { - VStack(spacing: 30) { - if changeLayout { - HStack { items } - } else { - VStack { items } - } - Button("切换布局") { - withAnimation { changeLayout.toggle() } - } - } - .padding() - } - - @ViewBuilder var items: some View { - Text("one") - .matchedGeometryEffect(id: "one", in: namespace) - Text("Two") - .matchedGeometryEffect(id: "Two", in: namespace) - Text("Three") - .matchedGeometryEffect(id: "Three", in: namespace) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/AttributeString(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/AttributeString(ap).md" deleted file mode 100644 index 04fba34fd..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/AttributeString(ap).md" +++ /dev/null @@ -1,67 +0,0 @@ -效果如下: - -![](https://ming1016.github.io/qdimg/240505/AttributeString-ap01.png) - -代码如下: -```swift -var aStrs = [AttributedString]() -var aStr1 = AttributedString(""" -标题 -正文内容,具体查看链接。 -这里摘出第一个重点,还要强调的内容。 -""") -// 标题 -let title = aStr1.range(of: "标题") -guard let title = title else { - return aStrs -} - -var c1 = AttributeContainer() // 可复用容器 -c1.inlinePresentationIntent = .stronglyEmphasized -c1.font = .largeTitle -aStr1[title].setAttributes(c1) - -// 链接 -let link = aStr1.range(of: "链接") -guard let link = link else { - return aStrs -} - -var c2 = AttributeContainer() // 链接 -c2.strokeColor = .blue -c2.link = URL(string: "https://ming1016.github.io/") -aStr1[link].setAttributes(c2.merging(c1)) // 合并 AttributeContainer - -// Runs -let i1 = aStr1.range(of: "重点") -let i2 = aStr1.range(of: "强调") -guard let i1 = i1, let i2 = i2 else { - return aStrs -} - -var c3 = AttributeContainer() -c3.foregroundColor = .yellow -c3.inlinePresentationIntent = .stronglyEmphasized -aStr1[i1].setAttributes(c3) -aStr1[i2].setAttributes(c3) - -for r in aStr1.runs { - print("-------------") - print(r.attributes) -} - -aStrs.append(aStr1) - -// Markdown -do { - let aStr2 = try AttributedString(markdown: """ - 内容[链接](https://ming1016.github.io/)。需要**强调**的内容。 - """) - - aStrs.append(aStr2) - -} catch {} -``` - - -SwiftUI 的 Text 可以直接读取 AttributedString 来进行显示。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/Data(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/Data(ap).md" deleted file mode 100644 index fe4bfea4b..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/Data(ap).md" +++ /dev/null @@ -1,23 +0,0 @@ -数据压缩和解压 -```swift -// 对数据的压缩 -let d1 = "看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?".data(using: .utf8)! as NSData -print("ori \(d1.count) bytes") -do { - /// 压缩算法 - /// * lz4 - /// * lzma - /// * zlib - /// * lzfse - let compressed = try d1.compressed(using: .zlib) - print("comp \(compressed.count) bytes") - - // 对数据解压 - let decomressed = try compressed.decompressed(using: .zlib) - let deStr = String(data: decomressed as Data, encoding: .utf8) - print(deStr ?? "") -} catch {} -/// ori 297 bytes -/// comp 37 bytes -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/Scanner(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/Scanner(ap).md" deleted file mode 100644 index f4e751622..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/Scanner(ap).md" +++ /dev/null @@ -1,31 +0,0 @@ -```swift -let s1 = """ -one1, -two2, -three3. -""" -let sn1 = Scanner(string: s1) -while !sn1.isAtEnd { - if let r1 = sn1.scanUpToCharacters(from: .newlines) { - print(r1 as String) - } -} -/// one1, -/// two2, -/// three3. - -// 找出数字 -let sn2 = Scanner(string: s1) -sn2.charactersToBeSkipped = CharacterSet.decimalDigits.inverted // 不是数字的就跳过 -var p: Int = 0 -while !sn2.isAtEnd { - if sn2.scanInt(&p) { - print(p) - } -} -/// 1 -/// 2 -/// 3 -``` - -上面的代码还不是那么 Swifty,可以通过用AnySequence和AnyIterator来包装下,将序列中的元素推迟到实际需要时再来处理,这样性能也会更好些。具体实现可以参看《 [String parsing in Swift](https://www.swiftbysundell.com/articles/string-parsing-in-swift/) 》这篇文章。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/UserDefaults(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/UserDefaults(ap).md" deleted file mode 100644 index 10ae3bb88..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/UserDefaults(ap).md" +++ /dev/null @@ -1,11 +0,0 @@ -使用方法如下: - -```swift -enum UDKey { - static let k1 = "token" -} -let ud = UserDefaults.standard -ud.set("xxxxxx", forKey: UDKey.k1) -let tk = ud.string(forKey: UDKey.k1) -print(tk ?? "") -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\345\272\246\351\207\217\345\200\274(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\345\272\246\351\207\217\345\200\274(ap).md" deleted file mode 100644 index e9af8e746..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\345\272\246\351\207\217\345\200\274(ap).md" +++ /dev/null @@ -1,28 +0,0 @@ -标准库里的物理量,在这个文档里有详细列出,包括角度、平方米等。 -```swift -// 参考:https://developer.apple.com/documentation/foundation/nsdimension -let m1 = Measurement(value: 1, unit: UnitLength.kilometers) -let m2 = m1.converted(to: .meters) // 千米转米 -print(m2) // 1000.0 m -// 度量值转为本地化的值 -let mf = MeasurementFormatter() -mf.locale = Locale(identifier: "zh_Hans_CN") -print(mf.string(from: m1)) // 1公里 -``` - -一些物理公式供参考: -``` -面积 = 长度 × 长度 -体积 = 长度 × 长度 × 长度 = 面积 × 长度 - -速度=长度/时间 -加速度=速度/时间 - -力 = 质量 × 加速度 -扭矩 = 力 × 长度 -压力 = 力 / 面积 - -密度=质量 / 体积 -能量 = 功率 × 时间 -电阻 = 电压 / 电流 -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\226\207\344\273\266(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\226\207\344\273\266(ap).md" deleted file mode 100644 index 392bf549c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\226\207\344\273\266(ap).md" +++ /dev/null @@ -1,77 +0,0 @@ -文件的一些基本操作的代码如下: -```swift -let path1 = "/Users/mingdai/Downloads/1.html" -let path2 = "/Users/mingdai/Documents/GitHub/" - -let u1 = URL(string: path1) -do { - // 写入 - let url1 = try FileManager.default.url(for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: u1, create: true) // 保证原子性安全保存 - print(url1) - - // 读取 - let s1 = try String(contentsOfFile: path1, encoding: .utf8) - print(s1) - -} catch {} - -// 检查路径是否可用 -let u2 = URL(fileURLWithPath:path2) -do { - let values = try u2.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey]) - if let capacity = values.volumeAvailableCapacityForImportantUsage { - print("可用: \(capacity)") - } else { - print("不可用") - } -} catch { - print("错误: \(error.localizedDescription)") -} -``` - -怎么遍历多级目录结构中的文件呢?看下面的代码的实现: -```swift -// 遍历路径下所有目录 -let u3 = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) -let fm = FileManager.default -fm.enumerator(atPath: u3.path)?.forEach({ path in - guard let path = path as? String else { - return - } - let url = URL(fileURLWithPath: path, relativeTo: u3) - print(url.lastPathComponent) -}) -``` - -可以使用 FileWrapper 来创建文件夹和文件。举个例子: -```swift -// FileWrapper 的使用 -// 创建文件 -let f1 = FileWrapper(regularFileWithContents: Data("# 第 n 个文件\n ## 标题".utf8)) -f1.fileAttributes[FileAttributeKey.creationDate.rawValue] = Date() -f1.fileAttributes[FileAttributeKey.modificationDate.rawValue] = Date() -// 创建文件夹 -let folder1 = FileWrapper(directoryWithFileWrappers: [ - "file1.md": f1 -]) -folder1.fileAttributes[FileAttributeKey.creationDate.rawValue] = Date() -folder1.fileAttributes[FileAttributeKey.modificationDate.rawValue] = Date() - -do { - try folder1.write( - to: URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("NewFolder"), - options: .atomic, - originalContentsURL: nil - ) -} catch {} -print(FileManager.default.currentDirectoryPath) -``` - -上面代码写起来比较繁琐,对 FileWrapper 更好的封装可以参考这篇文章《 [A Type-Safe FileWrapper | Heberti Almeida](https://heberti.com/posts/filewrapper/) 》。 - -文件读写处理完整能力可以参看这个库 [GitHub - JohnSundell/Files: A nicer way to handle files & folders in Swift](https://github.com/JohnSundell/Files) - -本地或者网络上,比如网盘和FTP的文件发生变化时,怎样知道能够观察到呢? - -通过 HTTPHeader 里的 If-Modified-Since、Last-Modified、If-None-Match 和 Etag 等字段来判断文件的变化,本地则是使用 DispatchSource.makeFileSystemObjectSource 来进行的文件变化监听。可以参考 [KZFileWatchers](https://github.com/krzysztofzablocki/KZFileWatchers) 库的做法。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\227\266\351\227\264(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\227\266\351\227\264(ap).md" deleted file mode 100644 index 194340ded..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\227\266\351\227\264(ap).md" +++ /dev/null @@ -1,51 +0,0 @@ -Date 的基本用法如下: -```swift -let now = Date() - -// Date 转 时间戳 -let interval = now.timeIntervalSince1970 // 时间戳 -let df = DateFormatter() -df.dateFormat = "yyyy 年 MM 月 dd 日 HH:mm:ss" -print("时间戳:\(Int(interval))") // 时间戳:1642399901 -print("格式化的时间:" + df.string(from: now)) // 格式化的时间:2022 年 01 月 17 日 14:11:41 -df.dateStyle = .short -print("short 样式时间:" + df.string(from: now)) // short 样式时间:2022/1/17 -df.locale = Locale(identifier: "zh_Hans_CN") -df.dateStyle = .full -print("full 样式时间:" + df.string(from: now)) // full 样式时间:2022年1月17日 星期一 - -// 时间戳转 Date -let date = Date(timeIntervalSince1970: interval) -print(date) // 2022-01-17 06:11:41 +0000 -``` - - -复杂的时间操作,比如说 GitHub 接口使用的是 ISO 标准,RSS 输出的是 RSS 标准字符串,不同标准对应不同时区的时间计算处理,可以使用开源库 [SwiftDate](https://github.com/malcommac/SwiftDate) 来完成。示例代码如下: - -```swift -import SwiftDate - -// 使用 SwiftDate 库 -let cn = Region(zone: Zones.asiaShanghai, locale: Locales.chineseChina) -SwiftDate.defaultRegion = cn -print("2008-02-14 23:12:14".toDate()?.year ?? "") // 2008 - -let d1 = "2022-01-17T23:20:35".toISODate(region: cn) -guard let d1 = d1 else { - return -} -print(d1.minute) // 20 -let d2 = d1 + 1.minutes -print(d2.minute) - -// 两个 DateInRegion 相差时间 interval -let i1 = DateInRegion(Date(), region: cn) - d1 -let s1 = i1.toString { - $0.maximumUnitCount = 4 - $0.allowedUnits = [.day, .hour, .minute] - $0.collapsesLargestUnit = true - $0.unitsStyle = .abbreviated - $0.locale = Locales.chineseChina -} -print(s1) // 9小时45分钟 -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226(ap).md" deleted file mode 100644 index 0a82c612f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226(ap).md" +++ /dev/null @@ -1,91 +0,0 @@ -使用标准库的格式来描述不同场景的情况可以不用去考虑由于不同地区的区别,这些在标准库里就可以自动完成了。 - -描述两个时间之间相差多长时间 -```swift -// 计算两个时间之间相差多少时间,支持多种语言字符串 -let d1 = Date().timeIntervalSince1970 - 60 * 60 * 24 -let f1 = RelativeDateTimeFormatter() -f1.dateTimeStyle = .named -f1.formattingContext = .beginningOfSentence -f1.locale = Locale(identifier: "zh_Hans_CN") -let str = f1.localizedString(for: Date(timeIntervalSince1970: d1), relativeTo: Date()) -print(str) // 昨天 - -// 简写 -let str2 = Date.now.addingTimeInterval(-(60 * 60 * 24)) - .formatted(.relative(presentation: .named)) -print(str2) // yesterday -``` - - -描述多个事物 -```swift -// 描述多个事物 -let s1 = ListFormatter.localizedString(byJoining: ["冬天","春天","夏天","秋天"]) -print(s1) -``` - - -描述名字 -```swift -// 名字 -let f2 = PersonNameComponentsFormatter() -var nc1 = PersonNameComponents() -nc1.familyName = "戴" -nc1.givenName = "铭" -nc1.nickname = "铭哥" -print(f2.string(from: nc1)) // 戴铭 -f2.style = .short -print(f2.string(from: nc1)) // 铭哥 -f2.style = .abbreviated -print(f2.string(from: nc1)) // 戴 - -var nc2 = PersonNameComponents() -nc2.familyName = "Dai" -nc2.givenName = "Ming" -nc2.nickname = "Starming" -f2.style = .default -print(f2.string(from: nc2)) // Ming Dai -f2.style = .short -print(f2.string(from: nc2)) // Starming -f2.style = .abbreviated -print(f2.string(from: nc2)) // MD - -// 取出名 -let componets = f2.personNameComponents(from: "戴铭") -print(componets?.givenName ?? "") // 铭 -``` - - -描述数字 -```swift -// 数字 -let f3 = NumberFormatter() -f3.locale = Locale(identifier: "zh_Hans_CN") -f3.numberStyle = .currency -print(f3.string(from: 123456) ?? "") // ¥123,456.00 -f3.numberStyle = .percent -print(f3.string(from: 123456) ?? "") // 12,345,600% - -let n1 = 1.23456 -let n1Str = n1.formatted(.number.precision(.fractionLength(3)).rounded()) -print(n1Str) // 1.235 -``` - - -描述地址 -```swift -// 地址 -import Contacts - -let f4 = CNPostalAddressFormatter() -let address = CNMutablePostalAddress() -address.street = "海淀区王庄路XX号院X号楼X门XXX" -address.postalCode = "100083" -address.city = "北京" -address.country = "中国" -print(f4.string(from: address)) -/// 海淀区王庄路XX号院X号楼X门XXX -/// 北京 100083 -/// 中国 -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\345\272\246\351\207\217\345\200\274(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\345\272\246\351\207\217\345\200\274(ap).md" deleted file mode 100644 index 49caad96b..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\345\272\246\351\207\217\345\200\274(ap).md" +++ /dev/null @@ -1,51 +0,0 @@ - - -MeasurementFormatter - -使用 Swift Foundation Formatter API -```swift -Measurement(value: 1, unit: .kilograms) - .formatted(.measurement(width: .abbreviated)) -``` - -标准库里的物理量,在这个文档里有详细列出,包括角度、平方米等。 -```swift -// 参考:https://developer.apple.com/documentation/foundation/nsdimension -let m1 = Measurement(value: 1, unit: UnitLength.kilometers) -let m2 = m1.converted(to: .meters) // 千米转米 -print(m2) // 1000.0 m -// 度量值转为本地化的值 -let mf = MeasurementFormatter() -mf.locale = Locale(identifier: "zh_Hans_CN") -print(mf.string(from: m1)) // 1公里 -``` - -一些物理公式供参考: -``` -面积 = 长度 × 长度 -体积 = 长度 × 长度 × 长度 = 面积 × 长度 - -速度=长度/时间 -加速度=速度/时间 - -力 = 质量 × 加速度 -扭矩 = 力 × 长度 -压力 = 力 / 面积 - -密度=质量 / 体积 -能量 = 功率 × 时间 -电阻 = 电压 / 电流 -``` - - -描述多个事物 -```swift -// 描述多个事物 -let s1 = ListFormatter.localizedString(byJoining: ["冬天","春天","夏天","秋天"]) -print(s1) -``` - -使用 Foundation Formatter API -```swift -["足球", "篮球"].formatted(.list(type: .and)) -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\346\225\260\346\215\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\346\225\260\346\215\256(ap).md" deleted file mode 100644 index a0714108a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\346\225\260\346\215\256(ap).md" +++ /dev/null @@ -1,34 +0,0 @@ - - -## `.formatted` - -```swift -print(888.formatted(.currency(code: "RMB"))) -print(99999.formatted()) -print(0.3.formatted(.percent)) -print(3.14.formatted(.number.precision(.fractionLength(1)))) -``` - -## NumberFormatter 描述数字 - -```swift -// 数字 -let f3 = NumberFormatter() -f3.locale = Locale(identifier: "zh_Hans_CN") -f3.numberStyle = .currency -print(f3.string(from: 123456) ?? "") // ¥123,456.00 -f3.numberStyle = .percent -print(f3.string(from: 123456) ?? "") // 12,345,600% - -let n1 = 1.23456 -let n1Str = n1.formatted(.number.precision(.fractionLength(3)).rounded()) -print(n1Str) // 1.235 -``` - -ByteCountFormatter 描述数据大小 -```swift -let formatter = ByteCountFormatter() -formatter.countStyle = .file - -text = formatter.string(fromByteCount: 1_000_000_000) // 1GB -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\346\227\266\351\227\264(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\346\227\266\351\227\264(ap).md" deleted file mode 100644 index d9382cab1..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\346\227\266\351\227\264(ap).md" +++ /dev/null @@ -1,140 +0,0 @@ - -## 格式化时间成字符串 - -格式化日期的示例。这些方法都是在 `Date` 类型上调用的,它们返回一个格式化的字符串,表示当前日期和时间。 - -```swift -Date.now.formatted() -``` -这行代码返回一个默认格式的日期和时间字符串。 - -```swift -Date.now.formatted(.relative(presentation: .named)) -``` -这行代码返回一个相对于现在的时间字符串,例如 "5 分钟前" 或 "明天"。 - -```swift -Date.now.formatted(date: .abbreviated, time: .omitted) -``` -这行代码返回一个简写的日期字符串,不包含时间。 - -```swift -Date.now.formatted(.dateTime.day(.twoDigits).month(.abbreviated).year(.twoDigits)) -``` -这行代码返回一个自定义格式的日期和时间字符串,包含两位数的日、简写的月和两位数的年。 - -```swift -Date.now.formatted(.iso8601) -``` -这行代码返回一个 ISO 8601 格式的日期和时间字符串。 - -代码示例: - -```swift -struct Movie { - let title: String - let screeningDates: [Date] -} - -let movie = Movie(title: "电影标题", screeningDates: [Date.now, Date.now.advanced(by: 60*60*24)]) - -for date in movie.screeningDates { - print("\(movie.title) 的放映时间是 \(date.formatted(date: .abbreviated, time: .shortened))") -} -``` - -这段代码会打印出电影的标题和每个放映时间,时间是以简写的日期和短格式的时间显示的。 - - -## 相对时间差 - -描述两个日期之间的相对时间差。这个类可以生成如 "昨天"、"5 分钟前" 这样的字符串。 - -```swift -let d1 = Date().timeIntervalSince1970 - 60 * 60 * 24 -``` -这行代码创建了一个 `Date` 对象,表示当前时间减去 24 小时(即一天前的时间)。 - -```swift -let f1 = RelativeDateTimeFormatter() -f1.dateTimeStyle = .named -f1.formattingContext = .beginningOfSentence -f1.locale = Locale(identifier: "zh_Hans_CN") -let str = f1.localizedString(for: Date(timeIntervalSince1970: d1), relativeTo: Date()) -print(str) // 昨天 -``` -这些代码创建了一个 `RelativeDateTimeFormatter` 对象,并设置了其样式和语言环境。然后,它使用这个格式化器来生成一个表示 `d1` 和当前时间相对时间差的字符串。 - -```swift -let str2 = Date.now.addingTimeInterval(-(60 * 60 * 24)) - .formatted(.relative(presentation: .named)) -print(str2) // yesterday -``` -这行代码做的事情和前面的代码类似,但是它使用了 Swift 5.5 引入的新的 `Date` 格式化 API。这个 API 更简洁,更易于使用。 - -我们可能会有一个电影放映时间的列表,我们可以使用这些方法来描述每个放映时间距离现在有多久。例如: - -```swift -struct Movie { - let title: String - let screeningDates: [Date] -} - -let movie = Movie(title: "电影标题", screeningDates: [Date.now, Date.now.advanced(by: 60*60*24)]) - -for date in movie.screeningDates { - let relativeTime = date.formatted(.relative(presentation: .named)) - print("\(movie.title) 的放映时间是 \(relativeTime)") -} -``` - -这段代码会打印出电影的标题和每个放映时间距离现在的相对时间。 - - -## 剩余时间 - -描述一个时间间隔。这个类可以生成如 "大约 5 分钟" 这样的字符串。 - -电影的剩余播放时间,我们可以使用这个方法来描述这个时间。例如: - -```swift -struct Movie { - let title: String - let remainingTime: TimeInterval -} - -let movie = Movie(title: "电影标题", remainingTime: 300.0) - -let formatter = DateComponentsFormatter() -formatter.unitsStyle = .full -formatter.includesApproximationPhrase = true -formatter.includesTimeRemainingPhrase = true -let text = formatter.string(from: movie.remainingTime) ?? "" - -print("\(movie.title) 的剩余播放时间是 \(text)") -``` - -这段代码会打印出电影的标题和剩余播放时间。 - - -## DateIntervalFormatter - -一个电影的放映时间段,我们可以使用这个方法来描述这个时间段。例如: - -```swift -struct Movie { - let title: String - let screeningInterval: (start: Date, end: Date) -} - -let movie = Movie(title: "电影标题", screeningInterval: (start: Date(), end: Date(timeInterval: 86400, since: Date()))) - -let formatter = DateIntervalFormatter() -formatter.dateStyle = .short -formatter.timeStyle = .none -let text = formatter.string(from: movie.screeningInterval.start, to: movie.screeningInterval.end) - -print("\(movie.title) 的放映时间段是 \(text)") -``` - -这段代码会打印出电影的标题和放映时间段。 \ No newline at end of file diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\347\224\237\346\264\273\346\227\245\345\270\270(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\347\224\237\346\264\273\346\227\245\345\270\270(ap).md" deleted file mode 100644 index a5b95306f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\346\240\274\345\274\217\345\214\226/\346\240\274\345\274\217\345\214\226-\347\224\237\346\264\273\346\227\245\345\270\270(ap).md" +++ /dev/null @@ -1,47 +0,0 @@ - -描述人名 -```swift -// 名字 -let f2 = PersonNameComponentsFormatter() -var nc1 = PersonNameComponents() -nc1.familyName = "戴" -nc1.givenName = "铭" -nc1.nickname = "铭哥" -print(f2.string(from: nc1)) // 戴铭 -f2.style = .short -print(f2.string(from: nc1)) // 铭哥 -f2.style = .abbreviated -print(f2.string(from: nc1)) // 戴 - -var nc2 = PersonNameComponents() -nc2.familyName = "Dai" -nc2.givenName = "Ming" -nc2.nickname = "Starming" -f2.style = .default -print(f2.string(from: nc2)) // Ming Dai -f2.style = .short -print(f2.string(from: nc2)) // Starming -f2.style = .abbreviated -print(f2.string(from: nc2)) // MD - -// 取出名 -let componets = f2.personNameComponents(from: "戴铭") -print(componets?.givenName ?? "") // 铭 -``` - -描述地址 -```swift -// 地址 -import Contacts - -let f4 = CNPostalAddressFormatter() -let address = CNMutablePostalAddress() -address.street = "海淀区王庄路XX号院X号楼X门XXX" -address.postalCode = "100083" -address.city = "北京" -address.country = "中国" -print(f4.string(from: address)) -/// 海淀区王庄路XX号院X号楼X门XXX -/// 北京 100083 -/// 中国 -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/canImport\345\210\244\346\226\255\345\272\223\346\230\257\345\220\246\345\217\257\344\275\277\347\224\250(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/canImport\345\210\244\346\226\255\345\272\223\346\230\257\345\220\246\345\217\257\344\275\277\347\224\250(ap).md" deleted file mode 100644 index a6cd62b5a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/canImport\345\210\244\346\226\255\345\272\223\346\230\257\345\220\246\345\217\257\344\275\277\347\224\250(ap).md" +++ /dev/null @@ -1,7 +0,0 @@ -```swift -#if canImport(SpriteKit) - // iOS 等苹果系统执行 -#else - // 非苹果系统 -#endif -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/targetEnvironment\347\216\257\345\242\203\347\232\204\345\210\244\346\226\255(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/targetEnvironment\347\216\257\345\242\203\347\232\204\345\210\244\346\226\255(ap).md" deleted file mode 100644 index 438b0692b..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/targetEnvironment\347\216\257\345\242\203\347\232\204\345\210\244\346\226\255(ap).md" +++ /dev/null @@ -1,7 +0,0 @@ -```swift -#if targetEnvironment(simulator) - // 模拟器 -#else - // 真机 -#endif -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/\347\211\210\346\234\254\345\205\274\345\256\271(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/\347\211\210\346\234\254\345\205\274\345\256\271(ap).md" deleted file mode 100644 index 1df4da2a1..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/\347\211\210\346\234\254\345\205\274\345\256\271(ap).md" +++ /dev/null @@ -1,14 +0,0 @@ -```swift -// 版本 -@available(iOS 15, *) -func f() { - -} - -// 版本检查 -if #available(iOS 15, macOS 12, *) { - f() -} else { - // nothing happen -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/\347\263\273\347\273\237\345\210\244\346\226\255(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/\347\263\273\347\273\237\345\210\244\346\226\255(ap).md" deleted file mode 100644 index d6d93a99a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\347\263\273\347\273\237\345\217\212\350\256\276\345\244\207/\347\263\273\347\273\237\345\210\244\346\226\255(ap).md" +++ /dev/null @@ -1,9 +0,0 @@ -```swift -#if os(tvOS) - // do something in tvOS -#elseif os(iOS) - // do somthing in iOS -#elseif os(macOS) - // do somthing in macOS -#endif -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\215\217\350\256\256/Hashable(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\215\217\350\256\256/Hashable(ap).md" deleted file mode 100644 index 23a96fae7..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\215\217\350\256\256/Hashable(ap).md" +++ /dev/null @@ -1,31 +0,0 @@ -```swift -struct H: Hashable { - var p1: String - var p2: Int - - // 提供随机 seed - func hash(into hasher: inout Hasher) { - hasher.combine(p1) - } -} - -let h1 = H(p1: "one", p2: 1) -let h2 = H(p1: "two", p2: 2) - -var hs1 = Hasher() -hs1.combine(h1) -hs1.combine(h2) -print(h1.hashValue) // 7417088153212460033 随机值 -print(h2.hashValue) // -6972912482785541972 随机值 -print(hs1.finalize()) // 7955861102637572758 随机值 -print(h1.hashValue) // 7417088153212460033 和前面 h1 一样 - -let h3 = H(p1: "one", p2: 1) -print(h3.hashValue) // 7417088153212460033 和前面 h1 一样 -var hs2 = Hasher() -hs2.combine(h3) -hs2.combine(h2) -print(hs2.finalize()) // 7955861102637572758 和前面 hs1 一样 -``` - -应用生命周期内,调用 combine() 添加相同属性哈希值相同,由于 Hasher 每次都会使用随机的 seed,因此不同应用生命周期,也就是下次启动的哈希值,就会和上次的哈希值不同。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\215\217\350\256\256/JSON\346\262\241\346\234\211id\345\255\227\346\256\265(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\215\217\350\256\256/JSON\346\262\241\346\234\211id\345\255\227\346\256\265(ap).md" deleted file mode 100644 index 887184558..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\215\217\350\256\256/JSON\346\262\241\346\234\211id\345\255\227\346\256\265(ap).md" +++ /dev/null @@ -1,14 +0,0 @@ -如果SwiftUI要求数据Model都是遵循Identifiable协议的,而有的json没有id这个字段,可以使用扩展struct的方式解决: - -```swift -struct CommitModel: Decodable, Hashable { - var sha: String - var author: AuthorModel - var commit: CommitModel -} -extension CommitModel: Identifiable { - var id: String { - return sha - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@dynamicCallable\345\212\250\346\200\201\345\217\257\350\260\203\347\224\250\347\261\273\345\236\213(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@dynamicCallable\345\212\250\346\200\201\345\217\257\350\260\203\347\224\250\347\261\273\345\236\213(ap).md" deleted file mode 100644 index 4d46b1e0f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@dynamicCallable\345\212\250\346\200\201\345\217\257\350\260\203\347\224\250\347\261\273\345\236\213(ap).md" +++ /dev/null @@ -1,28 +0,0 @@ - -@dynamicCallable 动态可调用类型。通过实现 dynamicallyCall 方法来定义变参的处理。 - -```swift -@dynamicCallable -struct D { - // 带参数说明 - func dynamicallyCall(withKeywordArguments args: KeyValuePairs) -> Int { - let firstArg = args.first?.value ?? 0 - return firstArg * 2 - } - - // 无参数说明 - func dynamicallyCall(withArguments args: [String]) -> String { - var firstArg = "" - if args.count > 0 { - firstArg = args[0] - } - return "show \(firstArg)" - } -} - -let d = D() -let i = d(numberIs: 2) -print(i) // 4 -let s = d("hi") -print(s) // show hi -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@dynamicMemberLookup\345\212\250\346\200\201\346\210\220\345\221\230\346\237\245\350\257\242(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@dynamicMemberLookup\345\212\250\346\200\201\346\210\220\345\221\230\346\237\245\350\257\242(ap).md" deleted file mode 100644 index dc4bacfc9..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@dynamicMemberLookup\345\212\250\346\200\201\346\210\220\345\221\230\346\237\245\350\257\242(ap).md" +++ /dev/null @@ -1,45 +0,0 @@ - -@dynamicMemberLookup 指示访问属性时调用一个已实现的处理动态查找的下标方法 subscript(dynamicMemeber:),通过指定属性字符串名返回值。使用方法如下: - -```swift -@dynamicMemberLookup -struct D { - // 找字符串 - subscript(dynamicMember m: String) -> String { - let p = ["one": "first", "two": "second"] - return p[m, default: ""] - } - // 找整型 - subscript(dynamicMember m: String) -> Int { - let p = ["one": 1, "two": 2] - return p[m, default: 0] - } - // 找闭包 - subscript(dynamicMember m: String) -> (_ s: String) -> Void { - return { - print("show \($0)") - } - } - // 静态数组成员 - var p = ["This is a member"] - // 动态数组成员 - subscript(dynamicMember m: String) -> [String] { - return ["This is a dynamic member"] - } -} - -let d = D() -let s1: String = d.one -print(s1) // first -let i1: Int = d.one -print(i1) // 1 -d.show("something") // show something -print(d.p) // ["This is a member"] -let dynamicP:[String] = d.dp -print(dynamicP) // ["This is a dynamic member"] -``` - -类使用 @dynamicMemberLookup,继承的类也会自动加上 @dynamicMemberLookup。协议上定义 @dynamicMemberLookup,通过扩展可以默认实现 subscript(dynamicMember:) 方法。 - - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@resultBuilder(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@resultBuilder(ap).md" deleted file mode 100644 index 583c5df0e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\350\207\252\345\270\246\345\261\236\346\200\247\345\214\205\350\243\205/@resultBuilder(ap).md" +++ /dev/null @@ -1,69 +0,0 @@ -结果生成器(Result builders),通过传递序列创建新值,SwiftUI就是使用的结果生成器将多个视图生成一个视图 - -```swift -@resultBuilder -struct RBS { - // 基本闭包支持 - static func buildBlock(_ components: Int...) -> Int { - components.reduce(0) { partialResult, i in - partialResult + i - } - } - // 支持条件判断 - static func buildEither(first component: Int) -> Int { - component - } - static func buildEither(second component: Int) -> Int { - component - } - // 支持循环 - static func buildArray(_ components: [Int]) -> Int { - components.reduce(0) { partialResult, i in - partialResult + i - } - } -} - -let a = RBS.buildBlock( - 1, - 2, - 3 -) -print(a) // 6 - -// 应用到函数中 -@RBS func f1() -> Int { - 1 - 2 - 3 -} -print(f1()) // 6 - -// 设置了 buildEither 就可以在闭包中进行条件判断。 -@RBS func f2(stopAtThree: Bool) -> Int { - 1 - 2 - 3 - if stopAtThree == true { - 0 - } else { - 4 - 5 - 6 - } -} -print(f2(stopAtThree: false)) // 21 - -// 设置了 buildArray 就可以在闭包内使用循环了 -@RBS func f3() -> Int { - for i in 1...3 { - i * 2 - } -} -print(f3()) // 12 -``` - -[SE-0348 buildPartialBlock for result builders](https://github.com/apple/swift-evolution/blob/main/proposals/0348-buildpartialblock.md) 简化了实现复杂 result buiders 所需的重载。 - - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\351\232\217\346\234\272(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\351\232\217\346\234\272(ap).md" deleted file mode 100644 index 51334596c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\237\272\347\241\200\345\272\223/\351\232\217\346\234\272(ap).md" +++ /dev/null @@ -1,8 +0,0 @@ -用法: -```swift -let ri = Int.random(in: 0..<10) -print(ri) // 0到10随机数 -let a = [0, 1, 2, 3, 4, 5] -print(a.randomElement() ?? 0) // 数组中随机取个数 -print(a.shuffled()) // 随机打乱数组顺序 -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\273\213\347\273\215/Combine\346\230\257\344\273\200\344\271\210(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\273\213\347\273\215/Combine\346\230\257\344\273\200\344\271\210(ap).md" deleted file mode 100644 index 186f62375..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\273\213\347\273\215/Combine\346\230\257\344\273\200\344\271\210(ap).md" +++ /dev/null @@ -1,56 +0,0 @@ -WWDC 2019苹果推出Combine,Combine是一种响应式编程范式,采用声明式的Swift API。 - -Combine 写代码的思路是你写代码不同于以往命令式的描述如何处理数据,Combine 是要去描述好数据会经过哪些逻辑运算处理。这样代码更好维护,可以有效的减少嵌套闭包以及分散的回调等使得代码维护麻烦的苦恼。 - -声明式和过程时区别可见如下代码: -```swift -// 所有数相加 -// 命令式思维 -func sum1(arr: [Int]) -> Int { - var sum: Int = 0 - for v in arr { - sum += v - } - return sum -} - -// 声明式思维 -func sum2(arr: [Int]) -> Int { - return arr.reduce(0, +) -} -``` - - -Combine 主要用来处理异步的事件和值。苹果 UI 框架都是在主线程上进行 UI 更新,Combine 通过 Publisher 的 receive 设置回主线程更新UI会非常的简单。 - -已有的 RxSwift 和 ReactiveSwift 框架和 Combine 的思路和用法类似。 - -Combine 的三个核心概念 -- 发布者 -- 订阅者 -- 操作符 - -简单举个发布数据和类属性绑定的例子: - -```swift -let pA = Just(0) -let _ = pA.sink { v in - print("pA is: \(v)") -} - -let pB = [7,90,16,11].publisher -let _ = pB - .sink { v in - print("pB: \(v)") - } - -class AClass { - var p: Int = 0 { - didSet { - print("property update to \(p)") - } - } -} -let o = AClass() -let _ = pB.assign(to: \.p, on: o) -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\273\213\347\273\215/Combine\347\232\204\350\265\204\346\226\231(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\273\213\347\273\215/Combine\347\232\204\350\265\204\346\226\231(ap).md" deleted file mode 100644 index 1c6995d60..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\273\213\347\273\215/Combine\347\232\204\350\265\204\346\226\231(ap).md" +++ /dev/null @@ -1,15 +0,0 @@ -官方文档链接 [Combine | Apple Developer Documentation](https://developer.apple.com/documentation/combine) 。还有 [Using Combine](https://heckj.github.io/swiftui-notes/) 这里有大量使用示例,内容较全。官方讨论Combine的论坛 [Topics tagged combine](https://forums.swift.org/tag/combine) 。StackOverflow上相关问题 [Newest ‘combine’ Questions](https://stackoverflow.com/questions/tagged/combine) 。 - -WWDC上关于Combine的Session如下: - -* [Introducing Combine](https://developer.apple.com/videos/play/wwdc2019/722/) -* [Combine in Practice](https://developer.apple.com/videos/play/wwdc2019/721/) - -和Combine相关的Session: - -* [Modern Swift API Design](https://developer.apple.com/videos/play/wwdc2019/415/) -* [Data Flow Through SwiftUI](https://developer.apple.com/videos/play/wwdc2019/226) -* [Introducing Combine and Advances in Foundation](https://developer.apple.com/videos/play/wwdc2019/711) -* [Advances in Networking, Part 1](https://developer.apple.com/videos/play/wwdc2019/712/) -* [Building Collaborative AR Experiences](https://developer.apple.com/videos/play/wwdc2019/610/) -* [Expanding the Sensory Experience with Core Haptics](https://developer.apple.com/videos/play/wwdc2019/223/) diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine KVO(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine KVO(ap).md" deleted file mode 100644 index a22742200..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine KVO(ap).md" +++ /dev/null @@ -1,13 +0,0 @@ -例子如下: -```swift -private final class KVOObject: NSObject { - @objc dynamic var intV: Int = 0 - @objc dynamic var boolV: Bool = false -} - -let o = KVOObject() -let _ = o.publisher(for: \.intV) - .sink { v in - print("value : \(v)") - } -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine Timer(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine Timer(ap).md" deleted file mode 100644 index b3e4b4be7..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine Timer(ap).md" +++ /dev/null @@ -1,8 +0,0 @@ -使用方式如下: -```swift -let timePb = Timer.publish(every: 1.0, on: RunLoop.main, in: .default) -let timeSk = timePb.sink { r in - print("r is \(r)") -} -let cPb = timePb.connect() -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine\347\275\221\347\273\234\350\257\267\346\261\202(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine\347\275\221\347\273\234\350\257\267\346\261\202(ap).md" deleted file mode 100644 index 59c43427e..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine\347\275\221\347\273\234\350\257\267\346\261\202(ap).md" +++ /dev/null @@ -1,362 +0,0 @@ -网络URLSession.dataTaskPublisher使用例子如下: -```swift -let req = URLRequest(url: URL(string: "http://www.starming.com")!) -let dpPublisher = URLSession.shared.dataTaskPublisher(for: req) -``` - -一个请求Github接口并展示结果的例子 -```swift -// -// CombineSearchAPI.swift -// SwiftOnly (iOS) -// -// Created by Ming Dai on 2021/11/4. -// - -import SwiftUI -import Combine - -struct CombineSearchAPI: View { - var body: some View { - GithubSearchView() - } -} - -// MARK: Github View -struct GithubSearchView: View { - @State var str: String = "Swift" - @StateObject var ss: SearchStore = SearchStore() - @State var repos: [GithubRepo] = [] - var body: some View { - NavigationView { - List { - TextField("输入:", text: $str, onCommit: fetch) - ForEach(self.ss.repos) { repo -> GithubRepoCell in - GithubRepoCell(repo: repo) - } - } - .navigationTitle("搜索") - } - .onAppear(perform: fetch) - } - - private func fetch() { - self.ss.search(str: self.str) - } -} - -struct GithubRepoCell: View { - let repo: GithubRepo - var body: some View { - VStack(alignment: .leading, spacing: 20) { - Text(self.repo.name) - Text(self.repo.description) - } - } -} - -// MARK: Github Service -struct GithubRepo: Decodable, Identifiable { - let id: Int - let name: String - let description: String -} - -struct GithubResp: Decodable { - let items: [GithubRepo] -} - -final class GithubSearchManager { - func search(str: String) -> AnyPublisher { - guard var urlComponents = URLComponents(string: "https://api.github.com/search/repositories") else { - preconditionFailure("链接无效") - } - urlComponents.queryItems = [URLQueryItem(name: "q", value: str)] - - guard let url = urlComponents.url else { - preconditionFailure("链接无效") - } - let sch = DispatchQueue(label: "API", qos: .default, attributes: .concurrent) - - return URLSession.shared - .dataTaskPublisher(for: url) - .receive(on: sch) - .tryMap({ element -> Data in - print(String(decoding: element.data, as: UTF8.self)) - return element.data - }) - .decode(type: GithubResp.self, decoder: JSONDecoder()) - .catch { _ in - Empty().eraseToAnyPublisher() - } - .eraseToAnyPublisher() - } -} - -final class SearchStore: ObservableObject { - @Published var query: String = "" - @Published var repos: [GithubRepo] = [] - private let searchManager: GithubSearchManager - private var cancellable = Set() - - init(searchManager: GithubSearchManager = GithubSearchManager()) { - self.searchManager = searchManager - $query - .debounce(for: .milliseconds(500), scheduler: RunLoop.main) - .flatMap { query -> AnyPublisher<[GithubRepo], Never> in - return searchManager.search(str: query) - .map { - $0.items - } - .eraseToAnyPublisher() - } - .receive(on: DispatchQueue.main) - .assign(to: \.repos, on: self) - .store(in: &cancellable) - } - func search(str: String) { - self.query = str - } -} -``` - - -抽象基础网络能力,方便扩展,代码如下: - -```swift -// -// CombineAPI.swift -// SwiftOnly (iOS) -// -// Created by Ming Dai on 2021/11/4. -// - -import SwiftUI -import Combine - -struct CombineAPI: View { - var body: some View { - RepListView(vm: .init()) - } -} - -struct RepListView: View { - @ObservedObject var vm: RepListVM - - var body: some View { - NavigationView { - List(vm.repos) { rep in - RepListCell(rep: rep) - } - .alert(isPresented: $vm.isErrorShow) { () -> Alert in - Alert(title: Text("出错了"), message: Text(vm.errorMessage)) - } - .navigationBarTitle(Text("仓库")) - } - .onAppear { - vm.apply(.onAppear) - } - } -} - -struct RepListCell: View { - @State var rep: RepoModel - var body: some View { - HStack() { - VStack() { - AsyncImage(url: URL(string: rep.owner.avatarUrl ?? ""), content: { image in - image - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 100, height: 100) - }, - placeholder: { - ProgressView() - .frame(width: 100, height: 100) - }) - Text("\(rep.owner.login)") - .font(.system(size: 10)) - } - VStack(alignment: .leading, spacing: 10) { - Text("\(rep.name)") - .font(.title) - Text("\(rep.stargazersCount)") - .font(.title3) - Text("\(String(describing: rep.description ?? ""))") - Text("\(String(describing: rep.language ?? ""))") - .font(.title3) - } - .font(.system(size: 14)) - } - - } -} - - -// MARK: Repo View Model -final class RepListVM: ObservableObject, UnidirectionalDataFlowType { - typealias InputType = Input - private var cancellables: [AnyCancellable] = [] - - // Input - enum Input { - case onAppear - } - func apply(_ input: Input) { - switch input { - case .onAppear: - onAppearSubject.send(()) - } - } - private let onAppearSubject = PassthroughSubject() - - // Output - @Published private(set) var repos: [RepoModel] = [] - @Published var isErrorShow = false - @Published var errorMessage = "" - @Published private(set) var shouldShowIcon = false - - private let resSubject = PassthroughSubject() - private let errSubject = PassthroughSubject() - - private let apiSev: APISev - - init(apiSev: APISev = APISev()) { - self.apiSev = apiSev - bindInputs() - bindOutputs() - } - - private func bindInputs() { - let req = SearchRepoRequest() - let resPublisher = onAppearSubject - .flatMap { [apiSev] in - apiSev.response(from: req) - .catch { [weak self] error -> Empty in - self?.errSubject.send(error) - return .init() - } - } - let resStream = resPublisher - .share() - .subscribe(resSubject) - - // 其它异步事件,比如日志等操作都可以做成Stream加到下面数组内。 - cancellables += [resStream] - } - - private func bindOutputs() { - let repStream = resSubject - .map { - $0.items - } - .assign(to: \.repos, on: self) - let errMsgStream = errSubject - .map { error -> String in - switch error { - case .resError: return "network error" - case .parseError: return "parse error" - } - } - .assign(to: \.errorMessage, on: self) - let errStream = errSubject - .map { _ in - true - } - .assign(to: \.isErrorShow, on: self) - cancellables += [repStream,errStream,errMsgStream] - } - -} - - -protocol UnidirectionalDataFlowType { - associatedtype InputType - func apply(_ input: InputType) -} - -// MARK: Repo Request and Models - -struct SearchRepoRequest: APIReqType { - typealias Res = SearchRepoModel - - var path: String { - return "/search/repositories" - } - var qItems: [URLQueryItem]? { - return [ - .init(name: "q", value: "Combine"), - .init(name: "order", value: "desc") - ] - } -} - -struct SearchRepoModel: Decodable { - var items: [RepoModel] -} - -struct RepoModel: Decodable, Hashable, Identifiable { - var id: Int64 - var name: String - var fullName: String - var description: String? - var stargazersCount: Int = 0 - var language: String? - var owner: OwnerModel -} - -struct OwnerModel: Decodable, Hashable, Identifiable { - var id: Int64 - var login: String - var avatarUrl: String? -} - - -// MARK: API Request Fundation - -protocol APIReqType { - associatedtype Res: Decodable - var path: String { get } - var qItems: [URLQueryItem]? { get } -} - -protocol APISevType { - func response(from req: Request) -> AnyPublisher where Request: APIReqType -} - -final class APISev: APISevType { - private let rootUrl: URL - init(rootUrl: URL = URL(string: "https://api.github.com")!) { - self.rootUrl = rootUrl - } - - func response(from req: Request) -> AnyPublisher where Request : APIReqType { - let path = URL(string: req.path, relativeTo: rootUrl)! - var comp = URLComponents(url: path, resolvingAgainstBaseURL: true)! - comp.queryItems = req.qItems - print(comp.url?.description ?? "url wrong") - var req = URLRequest(url: comp.url!) - req.addValue("application/json", forHTTPHeaderField: "Content-Type") - - let de = JSONDecoder() - de.keyDecodingStrategy = .convertFromSnakeCase - return URLSession.shared.dataTaskPublisher(for: req) - .map { data, res in - print(String(decoding: data, as: UTF8.self)) - return data - } - .mapError { _ in - APISevError.resError - } - .decode(type: Request.Res.self, decoder: de) - .mapError(APISevError.parseError) - .receive(on: RunLoop.main) - .eraseToAnyPublisher() - } -} - -enum APISevError: Error { - case resError - case parseError(Error) -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine\351\200\232\347\237\245(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine\351\200\232\347\237\245(ap).md" deleted file mode 100644 index 71fdab7e5..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\345\234\272\346\231\257/Combine\351\200\232\347\237\245(ap).md" +++ /dev/null @@ -1,26 +0,0 @@ -使用例子如下: -```swift -extension Notification.Name { - static let noti = Notification.Name("nameofnoti") -} - -let notiPb = NotificationCenter.default.publisher(for: .noti, object: nil) - .sink { - print($0) - } -``` - -退到后台接受通知的例子如下: -```swift -class A { - var storage = Set() - - init() { - NotificationCenter.default.publisher(for: UIWindowScene.didEnterBackgroundNotification) - .sink { _ in - print("enter background") - } - .store(in: &self.storage) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/CurrentValueSubject(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/CurrentValueSubject(ap).md" deleted file mode 100644 index 886faeed7..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/CurrentValueSubject(ap).md" +++ /dev/null @@ -1,52 +0,0 @@ -CurrentValueSubject 的订阅者可以收到订阅时已发出的那条数据 - -```swift -import Combine - -var cc = Set() - -let cs = CurrentValueSubject("one") -cs.send("two") -cs.send("three") -let sb1 = cs - .print("cs sb1") - .sink { - print($0) - } - -cs.send("four") -cs.send("five") - -let sb2 = cs - .print("cs sb2") - .sink { - print($0) - } - -cs.send("six") - -sb1.store(in: &cc) -sb2.store(in: &cc) -``` - -输出 -``` -cs sb1: receive subscription: (CurrentValueSubject) -cs sb1: request unlimited -cs sb1: receive value: (three) -three -cs sb1: receive value: (four) -four -cs sb1: receive value: (five) -five -cs sb2: receive subscription: (CurrentValueSubject) -cs sb2: request unlimited -cs sb2: receive value: (five) -five -cs sb1: receive value: (six) -six -cs sb2: receive value: (six) -six -cs sb1: receive cancel -cs sb2: receive cancel -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Empty(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Empty(ap).md" deleted file mode 100644 index 3aa81771d..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Empty(ap).md" +++ /dev/null @@ -1,37 +0,0 @@ -```swift -import Combine - -var cc = Set() -struct S { - let p1: String - let p2: String -} - -let ept = Empty() // 加上 completeImmediately: false 后面即使用 replaceEmpty 也不会接受值 -ept - .print("ept") - .sink { c in - print("completion:", c) - } receiveValue: { s in - print("receive:", s) - } - .store(in: &cc) - -ept.replaceEmpty(with: S(p1: "1", p2: "one")) - .sink { c in - print("completion:", c) - } receiveValue: { s in - print("receive:", s) - } - .store(in: &cc) -``` - -输出 -``` -ept: receive subscription: (Empty) -ept: request unlimited -ept: receive finished -completion: finished -receive: S(p1: "1", p2: "one") -completion: finished -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Just(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Just(ap).md" deleted file mode 100644 index 74fc2a538..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Just(ap).md" +++ /dev/null @@ -1,27 +0,0 @@ -Just 是发布者,发布的数据在初始化时完成 - -```swift -import Combine -var cc = Set() -struct S { - let p1: String - let p2: String -} - -let pb = Just(S(p1: "1", p2: "one")) -pb - .print("pb") - .sink { - print($0) - } - .store(in: &cc) -``` - -输出 -``` -pb: receive subscription: (Just) -pb: request unlimited -pb: receive value: (S(p1: "1", p2: "one")) -S(p1: "1", p2: "one") -pb: receive finished -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/PassthroughSubject(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/PassthroughSubject(ap).md" deleted file mode 100644 index 108d4b8b5..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/PassthroughSubject(ap).md" +++ /dev/null @@ -1,79 +0,0 @@ -PassthroughSubject 可以传递多值,订阅者可以是一个也可以是多个,send 指明 completion 后,订阅者就没法接收到新发送的值了。 - -```swift -import Combine - -var cc = Set() -struct S { - let p1: String - let p2: String -} - -enum CError: Error { - case aE, bE -} -let ps1 = PassthroughSubject() -ps1 - .print("ps1") - .sink { c in - print("completion:", c) // send 了 .finished 后会执行 - } receiveValue: { s in - print("receive:", s) - - } - .store(in: &cc) - -ps1.send(S(p1: "1", p2: "one")) -ps1.send(completion: .failure(CError.aE)) // 和 .finished 一样后面就不会发送了 -ps1.send(S(p1: "2", p2: "two")) -ps1.send(completion: .finished) -ps1.send(S(p1: "3", p2: "three")) - -// 多个订阅者 -let ps2 = PassthroughSubject() -ps2.send("one") // 订阅之前 send 的数据没有订阅者可以接收 -ps2.send("two") - -let sb1 = ps2 - .print("ps2 sb1") - .sink { s in - print(s) - } - -ps2.send("three") // 这个 send 的值会被 sb1 - -let sb2 = ps2 - .print("ps2 sb2") - .sink { s in - print(s) - } - -ps2.send("four") // 这个 send 的值会被 sb1 和 sb2 接受 - -sb1.store(in: &cc) -sb2.store(in: &cc) -ps2.send(completion: .finished) - -``` - -输出 -``` -ps1: receive subscription: (PassthroughSubject) -ps1: request unlimited -ps1: receive value: (S(p1: "1", p2: "one")) -receive: S(p1: "1", p2: "one") -ps1: receive error: (aE) -completion: failure(戴铭的开发小册子.AppDelegate.(unknown context at $10b15ce10).(unknown context at $10b15cf3c).CError.aE) -ps2 sb1: receive subscription: (PassthroughSubject) -ps2 sb1: request unlimited -ps2 sb1: receive value: (three) -three -ps2 sb2: receive subscription: (PassthroughSubject) -ps2 sb2: request unlimited -ps2 sb1: receive value: (four) -four -ps2 sb2: receive value: (four) -four -ps2 sb1: receive finished -ps2 sb2: receive finished -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Scheduler(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Scheduler(ap).md" deleted file mode 100644 index 1ce896e47..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/Scheduler(ap).md" +++ /dev/null @@ -1,35 +0,0 @@ -Scheduler 处理队列。 - -```swift -import Combine - -var cc = Set() - -let sb1 = ["one","two","three"].publisher - .print("sb1") - .subscribe(on: DispatchQueue.global()) - .handleEvents(receiveOutput: { - print("receiveOutput",$0) - }) - .receive(on: DispatchQueue.main) - .sink { - print($0) - } -sb1.store(in: &cc) -``` - -输出 -``` -sb1: receive subscription: ([1, 2, 3]) -sb1: request unlimited -sb1: receive value: (1) -receiveOutput 1 -sb1: receive value: (2) -receiveOutput 2 -sb1: receive value: (3) -receiveOutput 3 -sb1: receive finished -1 -2 -3 -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/append(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/append(ap).md" deleted file mode 100644 index 799a22d09..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/append(ap).md" +++ /dev/null @@ -1,36 +0,0 @@ -append 会在发布者发布结束后追加发送数据,发布者不结束,append 的数据不会发送。 - -```swift -import Combine - -var cc = Set() - -let pb = PassthroughSubject() - -let sb = pb - .print("sb") - .append("five", "six") - .sink { - print($0) - } - -sb.store(in: &cc) - -pb.send("one") -pb.send("two") -pb.send("three") -pb.send(completion: .finished) -``` - -输出 -``` -sb: receive subscription: ([戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher), 戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher), 戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher)]) -sb: request unlimited -sb: receive value: (S(p: AnyPublisher)) -one -sb: receive value: (S(p: AnyPublisher)) -two -sb: receive value: (S(p: AnyPublisher)) -three -sb: receive finished -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/combineLatest(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/combineLatest(ap).md" deleted file mode 100644 index 237ed7712..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/combineLatest(ap).md" +++ /dev/null @@ -1,40 +0,0 @@ -combineLatest 会合并多个发布者发布的数据,只有当多个发布者都发布了数据后才会触发合并,合并每个发布者发布的最后一个数据。 - -```swift -import Combine - -var cc = Set() - -let ps1 = PassthroughSubject() -let ps2 = PassthroughSubject() -let ps3 = PassthroughSubject() - -let sb1 = ps1.combineLatest(ps2, ps3) - .print("sb1") - .sink { - print($0) - } - -ps1.send("one") -ps1.send("two") -ps1.send("three") -ps2.send("1") -ps2.send("2") -ps1.send("four") -ps2.send("3") -ps3.send("一") -ps3.send("二") - -sb1.store(in: &cc) -``` - -输出 -``` -sb1: receive subscription: (CombineLatest) -sb1: request unlimited -sb1: receive value: (("four", "3", "一")) -("four", "3", "一") -sb1: receive value: (("four", "3", "二")) -("four", "3", "二") -sb1: receive cancel -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/flatMap(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/flatMap(ap).md" deleted file mode 100644 index cb0cf25d3..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/flatMap(ap).md" +++ /dev/null @@ -1,41 +0,0 @@ -flatMap 能将多个发布者的值打平发送给订阅者 - -```swift -import Combine - -var cc = Set() - -struct S { - let p: AnyPublisher -} - -let s1 = S(p: Just("one").eraseToAnyPublisher()) -let s2 = S(p: Just("two").eraseToAnyPublisher()) -let s3 = S(p: Just("three").eraseToAnyPublisher()) - -let pb = [s1, s2, s3].publisher - -let sb = pb - .print("sb") - .flatMap { - $0.p - } - .sink { - print($0) - } - -sb.store(in: &cc) -``` - -输出 -``` -sb: receive subscription: ([戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher), 戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher), 戴铭的开发小册子.AppDelegate.(unknown context at $101167070).(unknown context at $1011670f4).S(p: AnyPublisher)]) -sb: request unlimited -sb: receive value: (S(p: AnyPublisher)) -one -sb: receive value: (S(p: AnyPublisher)) -two -sb: receive value: (S(p: AnyPublisher)) -three -sb: receive finished -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/merge(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/merge(ap).md" deleted file mode 100644 index fc412d0ac..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/merge(ap).md" +++ /dev/null @@ -1,40 +0,0 @@ -订阅者可以通过 merge 合并多个发布者发布的数据 - -```swift -import Combine - -var cc = Set() - -let ps1 = PassthroughSubject() -let ps2 = PassthroughSubject() - -let sb1 = ps1.merge(with: ps2) - .sink { - print($0) - } - -ps1.send("one") -ps1.send("two") -ps2.send("1") -ps2.send("2") -ps1.send("three") - -sb1.store(in: &cc) -``` - -输出 -``` -sb1: receive subscription: (Merge) -sb1: request unlimited -sb1: receive value: (one) -one -sb1: receive value: (two) -two -sb1: receive value: (1) -1 -sb1: receive value: (2) -2 -sb1: receive value: (three) -three -sb1: receive cancel -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/prepend(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/prepend(ap).md" deleted file mode 100644 index c79d78099..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/prepend(ap).md" +++ /dev/null @@ -1,44 +0,0 @@ -prepend 会在发布者发布前先发送数据,发布者不结束也不会受影响。发布者和集合也可以被打平发布。 - -```swift -import Combine - -var cc = Set() - -let pb1 = PassthroughSubject() -let pb2 = ["nine", "ten"].publisher - -let sb = pb1 - .print("sb") - .prepend(pb2) - .prepend(["seven","eight"]) - .prepend("five", "six") - .sink { - print($0) - } - -sb.store(in: &cc) - -pb1.send("one") -pb1.send("two") -pb1.send("three") -``` - -输出 -``` -five -six -seven -eight -nine -ten -sb: receive subscription: (PassthroughSubject) -sb: request unlimited -sb: receive value: (one) -one -sb: receive value: (two) -two -sb: receive value: (three) -three -sb: receive cancel -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/publisher(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/publisher(ap).md" deleted file mode 100644 index 1c32bb194..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/publisher(ap).md" +++ /dev/null @@ -1,29 +0,0 @@ -publisher 是发布者,sink 是订阅者 - -```swift -import Combine - -var cc = Set() -struct S { - let p1: String - let p2: String -} -[S(p1: "1", p2: "one"), S(p1: "2", p2: "two")] - .publisher - .print("array") - .sink { - print($0) - } - .store(in: &cc) -``` - - 输出 - ``` - array: receive subscription: ([戴铭的开发小册子.AppDelegate.(unknown context at $10ac82d20).(unknown context at $10ac82da4).S(p1: "1", p2: "one"), 戴铭的开发小册子.AppDelegate.(unknown context at $10ac82d20).(unknown context at $10ac82da4).S(p1: "2", p2: "two")]) -array: request unlimited -array: receive value: (S(p1: "1", p2: "one")) -S(p1: "1", p2: "one") -array: receive value: (S(p1: "2", p2: "two")) -S(p1: "2", p2: "two") -array: receive finished - ``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/removeDuplicates(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/removeDuplicates(ap).md" deleted file mode 100644 index 72316f84a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/removeDuplicates(ap).md" +++ /dev/null @@ -1,36 +0,0 @@ -使用 removeDuplicates,重复的值就不会发送了。 - -```swift -import Combine - -var cc = Set() - -let pb = ["one","two","three","three","four"] - .publisher - -let sb = pb - .print("sb") - .removeDuplicates() - .sink { - print($0) - } - -sb.store(in: &cc) -``` - -输出 -``` -sb: receive subscription: (["one", "two", "three", "three", "four"]) -sb: request unlimited -sb: receive value: (one) -one -sb: receive value: (two) -two -sb: receive value: (three) -three -sb: receive value: (three) -sb: request max: (1) (synchronous) -sb: receive value: (four) -four -sb: receive finished -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/zip(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/zip(ap).md" deleted file mode 100644 index a716da139..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Combine/\344\275\277\347\224\250\350\257\264\346\230\216/zip(ap).md" +++ /dev/null @@ -1,37 +0,0 @@ -zip 会合并多个发布者发布的数据,只有当多个发布者都发布了数据后才会组合成一个数据给订阅者。 - -```swift -import Combine - -var cc = Set() - -let ps1 = PassthroughSubject() -let ps2 = PassthroughSubject() -let ps3 = PassthroughSubject() - -let sb1 = ps1.zip(ps2, ps3) - .print("sb1") - .sink { - print($0) - } - -ps1.send("one") -ps1.send("two") -ps1.send("three") -ps2.send("1") -ps2.send("2") -ps1.send("four") -ps2.send("3") -ps3.send("一") - -sb1.store(in: &cc) -``` - -输出 -``` -sb1: receive subscription: (Zip) -sb1: request unlimited -sb1: receive value: (("one", "1", "一")) -("one", "1", "一") -sb1: receive cancel -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Actors(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Actors(ap).md" deleted file mode 100644 index 2c1c938ce..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Actors(ap).md" +++ /dev/null @@ -1,9 +0,0 @@ -我们写的程序会在进程中被拆成一个一个小指令,这些指令会在某刻会一个接一个同步的或者并发的执行。系统会用多个线程执行并行的任务,执行顺序是调度器来管理的,现代多核可以同时处理多个线程,当一个资源在多个线程上同时被更改时就会出问题。并发任务对数据资源操作容易造成数据竞争,以前需要手动放到串行队列、使用锁、调度屏障或 Atomics 的方式来避免。以前处理容易导致昂贵的上下文切换,过多线程容易导致线程爆炸,容易意外阻断线程导致后面代码没法执行,多任务相互的等待造成了死锁,block 和内存引用容易出错等等问题。 - -现在 Swift Concurrency 可以通过 actor 来创建一个区域,在这个区域会自动进行数据安全保护,保证一定时间只有一个线程访问里面数据,防止数据竞争。actor 内部对成员访问是同步的,成员默认是隔离的,actor 外部对 actor 内成员的访问只能是异步的,隐式同步以防止数据竞争。MainActor 继承自能确保全局唯一实例的 GlobalActor,保证任务在主线程执行,这样你就可以抛弃掉在你的 ViewModel 里写 DispatchQueue.main.async 了。 - -Actors 的概念通常被用于分布式计算,Actor 模型参看 [Wikipedia](https://en.wikipedia.org/wiki/Actor_model) 里的详细解释,Swift 中的实现效果也非常的理想。Actors 的提案 [SE-0306](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md) 已在 Swift 5.5落实。 - -很多语言都支持 actors 还有 async/await,实现的方式也类似,actor 使用的不是锁,而是用的 async/await 这样能够在一个线程中切换上下文来避免线程空闲的线程模型。actor 还利用编译器,提前做会引起并发问题的检查。 - -actor 是遵循 Sendable 协议的,只有结构体和 final 类才能够遵循 Sendable,继承于 Sendable 协议的 Excutor 协议表示方法本身,SerialExecutor 表示以串行方式执行。actor 使用 C++写的,源码在 [这里](https://github.com/apple/swift/blob/main/stdlib/public/Concurrency/Actor.cpp) ,可以看到 actor 主要是通过控制各个 job 执行的状态的管理器。job 执行优先级来自 Task 对象,排队时需要确保高优 job 先被执行。全局 Executor 用来为 job 排队,通知 actor 拥有或者放弃线程,实现在 [这里](https://github.com/apple/swift/blob/main/stdlib/public/Concurrency/GlobalExecutor.cpp) 。由于等待而放弃当前线程让其他 actor 执行的 actor,在收到全局 Executor 创建一个新的 job 的通知,使其可以进入一个可能不同线程,这个过程就是并发模型中描述的 Actor Reentrancy。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Async Sequences(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Async Sequences(ap).md" deleted file mode 100644 index f66450273..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Async Sequences(ap).md" +++ /dev/null @@ -1,9 +0,0 @@ -AsyncSequence 的使用方式是 for-await-in 和 for-try-await-in,系统提供了一些接口,如下: - -* FileHandle.standardInput.bytes.lines -* URL.lines -* URLSession.shared.data(from: URL) -* let (localURL, _ ) = try await session.download(from: url) 下载和get请求数据区别是需要边请求边存储数据以减少内存占用 -* let (responseData, response) = try await session.upload(for: request, from: data) -* URLSession.shared.bytes(from: URL) -* NotificationCenter.default.notifications diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Concurrency\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Concurrency\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" deleted file mode 100644 index c02378918..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Concurrency\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" +++ /dev/null @@ -1,49 +0,0 @@ -session [Eliminate data races using Swift Concurrency](https://developer.apple.com/videos/play/wwdc2022-110351) 、[Visualize and optimize Swift concurrency](https://developer.apple.com/videos/play/wwdc2022-110350) 、[Meet Swift Async Algorithms](https://developer.apple.com/videos/play/wwdc2022-110355) 。 - -表示持续时间有了新的放来来表达,对应提案是 [SE-0329 Clock, Instant, and Duration](https://github.com/apple/swift-evolution/blob/main/proposals/0329-clock-instant-duration.md) ,continuous clock 是在系统睡眠状态还会增加时间,suspending clock 在系统睡眠状态不会增加时间。Instants 表示一个确定的时间。Duration 表示两个时间经历了多久。 - -新增 [SE-0338 Clarify the Execution of Non-Actor-Isolated Async Functions](https://github.com/apple/swift-evolution/blob/main/proposals/0338-clarify-execution-non-actor-async.md) 通过收紧可发送性检查的规则来避免潜在的数据竞争。 - -[SE-0343 Concurrency in Top-level Code](https://github.com/apple/swift-evolution/blob/main/proposals/0343-top-level-concurrency.md) 这个提案主要是更好地支持命令行工具的开发,可以直接将 concurrency 代码写到 main.swift 文件里。 - -[SE-0340 Unavailable From Async Attribute](https://github.com/apple/swift-evolution/blob/main/proposals/0340-swift-noasync.md) 提供 noasync 语法以允许我们将类型和函数标记为在异步上下文不可用。 - -Task 是按顺序执行的,是异步的,在 await 时可以暂停任意次数。task 是自包含的,有自己的资源,可以独立于任何其他 task 独立运行。task 通过在 body 末尾返回一个值来传递对象,值类型没问题,如果是引用类型有可能出现数据竞争。 - -通过 Sendable 协议 Swift 可以帮助告诉我们什么时候 task 之间共享数据是安全的。Sendable 描述的类型可以跨隔离 domain,不会有数据竞争,Swift 编译器会在构建时检查数据竞争。task 的返回类型要符合 Sendable。 - -引用类型只能在很少的情况下符合 Sendable。比如 final class 只有不可变的存储。对于自己内部同步的引用类型,比如锁,可以用 `@unchecked Sendable` 。 -```swift -class ConcurrentCache: @unchecked Sendable { - var lock: NSLock - var storage: [Key: Value] - - // ... -} -``` - -Actor 提供了一种隔离状态的方法可以消除数据竞争。使用 task 来执行 actor 定义的代码。一次只能在一个 actor 上执行一个 task。actor 也是依赖 Sendable。actor 是引用类型,但隔离了他们所有属性和代码来防止并发访问。`@MainActor` 表示的是主线程,你要在应用中更新 UI 时来用它。 -```swift -@MainActor func updateView() { … } - -Task { @MainActor in - // update UI here -} -``` - -`@MainActor` 也可以用于类,类的属性和方法只能在主 main actor 上访问,除非标记为 `nonisolated` 。 -```swift -@MainActor -class ChickenValley: Sendable { - var flock: [Chicken] - var food: [Pineapple] - - func advanceTime() { - for chicken in flock { - chicken.eat(from: &food) - } - } -} -``` - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Distributed Actors(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Distributed Actors(ap).md" deleted file mode 100644 index dbc66e7a9..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Distributed Actors(ap).md" +++ /dev/null @@ -1,8 +0,0 @@ -actor 具有分布式形式工作能力,也就是可以 RPC 通过网络读取和写入属性或者调用方法。设计为保护在跨多个进程中的低级别数据竞争。Distributed actors 可以在两个进程间建立通道,隔离它们状态,并在它们之间异步通信。每个 distributed actors 在 actor 初始化时分配一个不可以手动创建的 id,在它所属整个 distributed actor 系统中唯一标识所指 actor,这样无论 distributed actors 在哪,都可以以相同的方式与之交互。 - -session [Meet distributed actors in Swift](https://developer.apple.com/videos/play/wwdc2022/110356/) 。这里有个 distributed actors 的代码示例 [TicTacFish: Implementing a game using distributed actors](https://developer.apple.com/documentation/swift/tictacfish_implementing_a_game_using_distributed_actors) - -[SE-0336 Distributed Actor Isolation](https://github.com/apple/swift-evolution/blob/main/proposals/0336-distributed-actor-isolation.md) 和 [SE-0344 Distributed Actor Runtime](https://github.com/apple/swift-evolution/blob/main/proposals/0344-distributed-actor-runtime.md) 是两个 Distributed Actors 的相关提案。 - -Apple 提供了一个参考的服务端 cluster actor 系统实现示例,[cluster actor system implementation](https://github.com/apple/swift-distributed-actors) 。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\345\222\214Combine(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\345\222\214Combine(ap).md" deleted file mode 100644 index d08f9a658..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\345\222\214Combine(ap).md" +++ /dev/null @@ -1 +0,0 @@ -由于 Swift Concurrency 的推出和大量的 Session 发布,特别是 [AsyncSequence](https://developer.apple.com/documentation/swift/asyncsequence/) 的出现,以及正在路上的 [AsyncStream、AsyncThrowingStream](https://github.com/apple/swift-evolution/blob/main/proposals/0314-async-stream.md) 和 [continuation](https://github.com/apple/swift-evolution/blob/main/proposals/0300-continuation.md) 提案(在Xcode 13.0 beta 3 AsyncStream 正式 [release](https://developer.apple.com/documentation/swift/asyncstream?changes=latest_beta) ),这些越来越多和 Combine 功能重叠的特性出现在 Swift Concurrency 蓝图里时,大家开始猜测是否 Combine 会被 Swift Concurrency 替代。关于未来是 Swift Concurrency 还是 Combine,我的感觉是,Combine 更侧重在响应式编程上,而响应式编程并不是所有开发人员都会接受的,而 Swift Concurrency 是所有人都愿意接受的开发方式,从 Swift Concurrency 推出后开发者使用的数量和社区反应火热程度来看都比 Combine 要大。在苹果对 Combine 有下一步动作之前,我还是更偏向 Swift Concurrency。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\345\255\246\344\271\240\350\267\257\345\276\204(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\345\255\246\344\271\240\350\267\257\345\276\204(ap).md" deleted file mode 100644 index ac2b6ea45..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\345\255\246\344\271\240\350\267\257\345\276\204(ap).md" +++ /dev/null @@ -1 +0,0 @@ -如果打算尝试 Swift Concurrency 的话,按照先后顺序,可以先看官方手册介绍文章 [Concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html) 。再看 [Meet async/await in Swift](https://developer.apple.com/videos/play/wwdc2021/10132/) 这个Session,了解背后原理看 [Explore structured concurrency in Swift](https://developer.apple.com/videos/play/wwdc2021/10134) 。动手照着试示例代码,看Paul的 [Swift Concurrency by Example](https://www.hackingwithswift.com/quick-start/concurrency) 这个系列。接着看 [Protect mutable state with Swift actors](https://developer.apple.com/videos/play/wwdc2021/10133) 来了解 actors 怎么防止数据竞争。通过 [Discover concurrency in SwiftUI](https://developer.apple.com/videos/play/wwdc2021/10019) 看 concurrency 如何在 SwiftUI 中使用, [Use async/await with URLSession](https://developer.apple.com/videos/play/wwdc2021/10095) 来看怎么在 URLSession 中使用 async/await。最后听听负责 Swift Concurrency 的 Doug Gregor 参加的一个 [播客的访谈](https://www.swiftbysundell.com/podcast/99/) ,了解下 Swift Concurrency 背后的故事。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\346\230\257\344\273\200\344\271\210(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\346\230\257\344\273\200\344\271\210(ap).md" deleted file mode 100644 index 9be862845..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\346\230\257\344\273\200\344\271\210(ap).md" +++ /dev/null @@ -1,22 +0,0 @@ -ABI 稳定后,Swift 的核心团队可以开始关注 Swift 语言一直缺失的原生并发能力了。最初是由 [Chris Lattner](https://twitter.com/clattner_llvm) 在17年发的 [Swift并发宣言](https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782) ,从此开阔了大家的眼界。后来 Swift Evolution 社区讨论了十几个提案,几十个方案,以及几百页的设计文件,做了大量的改进,社区中用户积极的参与反馈,Chris 也一直在 Evolution 中积极的参与设计。 - -Swift Concurrency 的实现用了 [LLVM的协程](https://llvm.org/docs/Coroutines.html) 把 async/await 函数转换为基于回调的代码,这个过程发生在编译后期,这个阶段你的代码都没法辨识了。异步的函数被实现为 coroutines,在每次异步调用时,函数被分割成可调用的函数部分和后面恢复的部分。coroutine 拆分的过程发生在生成LLVM IR阶段。Swift使用了哪些带有自定义调用约定的函数保证尾部调用,并专门为Swift进行了调整。 - -Swift Concurrency 不是建立在 GCD 上,而是使用的一个全新的线程池。GCD 中启动队列工作会很快在提起线程,一个队列阻塞了线程,就会生成一个新线程。基于这种机制 GCD 线程数很容易比 CPU 核心数量多,线程多了,线程就会有大量的调度开销,大量的上下文切换,会使 CPU 运行效率降低。而 Swift Concurrency 的线程数量不会超过 CPU 内核,将上下文切换放到同一个线程中去做。为了实现线程不被阻塞,需要通过语言特性来做。做法是,每个线程都有一个堆栈记录函数调用情况,一个函数占一个帧。函数返回后,这个函数所占的帧就会从堆栈弹出。await 的 async 函数被作为异步帧保存在堆上等待恢复,而不阻碍其它函数入栈执行。在 await 后运行的代码叫 continuation,continuation 会在要恢复时放回到线程的堆栈里。异步帧会根据需要放回栈上。在一个异步函数中调用同步代码将添加帧到线程的堆栈中。这样线程就能够一直向前跑,而不用创建更多线程减少调度。 - -Douglas 在 Swift 论坛里发的 Swift Concurrency 下个版本的规划贴 [Concurrency in Swift 5 and 6](https://forums.swift.org/t/concurrency-in-swift-5-and-6/49337) ,论坛里还有一个帖子是专门用来 [征集Swift Concurrency意见](https://forums.swift.org/t/swift-concurrency-feedback-wanted/49336) 的,帖子本身列出了 Swift Concurrency 相关的所有提案,也提出欢迎有新提案发出来,除了这些提案可以看外,帖子回复目前已经过百,非常热闹,可以看出大家对 Swift Concurrency 的关注度相当的高。 - -非常多的人参与了 Swift Concurrency 才使其看起来和用起来那么简单。Doug Gregor 在参与 John Sundell 的播客后,发了很多条推聊 Swift Concurrency,可以看到参与的人非常多,可见背后付出的努力有多大。下面我汇总了 Doug Gregor 在推上发的一些信息,你通过这些信息也可以了解 Swift Concurrency 幕后信息,所做的事和负责的人。 - - [@pathofshrines](https://twitter.com/pathofshrines) 是 Swift Concurrency 整体架构师,包括低级别运行时和编译器相关细节。 [@illian](https://twitter.com/illian) 是 async sequences、stream 和 Fundation 的负责人。 [@optshiftk](https://twitter.com/optshiftk) 对 UI 和并发交互的极好的洞察力带来了很棒的 async 接口, [@phausler](https://twitter.com/phausler) 带来了 async sequences。Arnold Schwaighofer、 [@neightchan](https://twitter.com/neightchan) 、 [@typesanitizer](https://twitter.com/typesanitizer) 还有 Tim Northover 实现了 async calling convention。 - - [@ktosopl](https://twitter.com/ktosopl) 有很深厚的 actor、分布式计算和 Swift-on-Server 经验,带来了 actor 系统。Erik Eckstein 为 async 函数和actors建立了关键的优化和功能。 - -SwiftUI是 [@ricketson_](https://twitter.com/ricketson_) 和 [@luka_bernardi](https://twitter.com/luka_bernardi) 完成的async接口。async I/O的接口是 [@Catfish_Man](https://twitter.com/Catfish_Man) 完成的。 [@slava_pestov](https://twitter.com/slava_pestov) 处理了 Swift 泛型问题,还指导其他人编译器实现的细节。async 重构工具是Ben Barham 做的。大量代码移植到 async 是由 [@AirspeedSwift](https://twitter.com/AirspeedSwift) 领导,由 Angela Laar,Clack Cole,Nicole Jacques 和 [@mishaldshah](https://twitter.com/mishaldshah) 共同完成的。 - - [@lorentey](https://twitter.com/lorentey) 负责 Swift 接口的改进。 [@jckarter](https://twitter.com/jckarter) 有着敏锐的语言设计洞察力,带来了语言设计经验和编译器及运行时实现技能。 [@mikeash](https://twitter.com/mikeash) 也参与了运行时开发中。操作系统的集成是 [@rokhinip](https://twitter.com/rokhinip) 完成的, [@chimz](https://twitter.com/chimz) 提供了关于 Dispatch 和 OS 很好的建议,Pavel Yaskevich 和 - [@hollyborla](https://ming1016.github.io/2021/07/24/my-little-idea-about-writing-technical-article/) 进行了并发所需要关键类型检查器的改进。 [@kastiglione](https://twitter.com/kastiglione) 、Adrian Prantl和 [@fred_riss](https://twitter.com/fred_riss) 实现了调试。 [@etcwilde](https://twitter.com/etcwilde) 和 [@call1cc](https://twitter.com/call1cc) 实现了语义模型中的重要部分。 - - [@evonox](https://twitter.com/evonox) 负责了服务器Linux 的支持。 [@compnerd](https://twitter.com/compnerd) 将 Swift Concurrency 移植到了 Windows。 - -Swift Concurrency 模型简单,细节都被隐藏了,比 Kotlin 和 C++的 Coroutine 接口要简洁很多。比如 Task 接口形式就很简洁。Swift Concurrency 大体可分为 async/await、Async Sequences、结构化并发和 Actors。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\347\233\270\345\205\263\346\217\220\346\241\210(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\347\233\270\345\205\263\346\217\220\346\241\210(ap).md" deleted file mode 100644 index b0840920a..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/Swift Concurrency\347\233\270\345\205\263\346\217\220\346\241\210(ap).md" +++ /dev/null @@ -1,16 +0,0 @@ -所有相关提案清单如下: - -* [SE-0296: Async/await](https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md) [【译】SE-0296 Async/await](https://kemchenj.github.io/2021-03-06/) -* [SE-0317: async let](https://github.com/apple/swift-evolution/blob/main/proposals/0317-async-let.md) -* [SE-0300: Continuations for interfacing async tasks with synchronous code](https://github.com/apple/swift-evolution/blob/main/proposals/0300-continuation.md) [【译】SE-0300 Continuation – 执行同步代码的异步任务接口](https://kemchenj.github.io/2021-03-31/) -* [SE-0302: Sendable and @Sendable closures](https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md) -* [SE-0298: Async/Await: Sequences](https://github.com/apple/swift-evolution/blob/main/proposals/0298-asyncsequence.md) [【译】SE-0298 Async/Await 序列](https://kemchenj.github.io/2021-03-10/) -* [SE-0304: Structured concurrency](https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md) -* [SE-0306: Actors](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md) [【译】SE-0306 Actors](https://kemchenj.github.io/2021-04-25/) -* [SE-0313: Improved control over actor isolation](https://github.com/apple/swift-evolution/blob/main/proposals/0313-actor-isolation-control.md) -* [SE-0297: Concurrency Interoperability with Objective-C](https://github.com/apple/swift-evolution/blob/main/proposals/0297-concurrency-objc.md) [【译】SE-0297 Concurrency 与 Objective-C 的交互](https://kemchenj.github.io/2021-03-07/) -* [SE-0314: AsyncStream and AsyncThrowingStream](https://github.com/apple/swift-evolution/blob/main/proposals/0314-async-stream.md) -* [SE-0316: Global actors](https://github.com/apple/swift-evolution/blob/main/proposals/0316-global-actors.md) -* [SE-0310: Effectful read-only properties](https://github.com/apple/swift-evolution/blob/main/proposals/0310-effectful-readonly-properties.md) -* [SE-0311: Task Local Values](https://github.com/apple/swift-evolution/blob/main/proposals/0311-task-locals.md) -* [Custom Executors](https://forums.swift.org/t/support-custom-executors-in-swift-concurrency/44425) diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/async await(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/async await(ap).md" deleted file mode 100644 index ac89f733f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/async await(ap).md" +++ /dev/null @@ -1,3 +0,0 @@ -通过类似 throws 语法的 async 来指定函数为异步函数,异步函数才能够使用 await,使用异步函数要用 await。await 修饰在 suspension point 时当前线程可以让给其它任务执行,而不用阻塞当前线程,等 await 后面的函数执行完成再回来继续执行,这里需要注意的是回来执行不一定是在离开时的线程上。async/await 提案是 [SE-0296](https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md) 。如果想把现有的异步开发带到 async/await 世界,请使用 withCheckedThrowingContinuation。 - -async/await 还有一个非常明显的好处,就是不会再有[weak self] dance 了。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/\347\273\223\346\236\204\345\214\226\345\271\266\345\217\221(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/\347\273\223\346\236\204\345\214\226\345\271\266\345\217\221(ap).md" deleted file mode 100644 index 43a88a4de..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\244\232\347\272\277\347\250\213/Swift Concurrency/\347\273\223\346\236\204\345\214\226\345\271\266\345\217\221(ap).md" +++ /dev/null @@ -1,3 +0,0 @@ -使用这些接口可以一边接收数据一边进行显示,AsyncSequence 的提案是 [SE-0298](https://github.com/apple/swift-evolution/blob/main/proposals/0298-asyncsequence.md) (Swift 5.5可用)。AsyncStream 是创建自己异步序列的最简单的方法,处理迭代、取消和缓冲。AsyncStream 正在路上,提案是 [SE-0314](https://github.com/apple/swift-evolution/blob/main/proposals/0314-async-stream.md) 。 - -Task 为一组并发任务创建一个运行环境,async let 可以让任务并发执行,结构化并发(Structured concurrency,提案在路上 [SE-0304](https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md) )withTaskGroup 中 group.async 可以将并发任务进行分组。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\256\211\345\205\250/Keychain(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\256\211\345\205\250/Keychain(ap).md" deleted file mode 100644 index b25a69ea5..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\256\211\345\205\250/Keychain(ap).md" +++ /dev/null @@ -1,51 +0,0 @@ -使用方法: -```swift -let d1 = Data("keyChain github token".utf8) -let service = "access-token" -let account = "github" -let q1 = [ - kSecValueData: d1, - kSecClass: kSecClassGenericPassword, - kSecAttrService: service, - kSecAttrAccount: account -] as CFDictionary - -// 添加一个 keychain -let status = SecItemAdd(q1, nil) - -// 如果已经添加过会抛出 -25299 错误代码,需要调用 SecItemUpdate 来进行更新 -if status == errSecDuplicateItem { - let q2 = [ - kSecClass: kSecClassGenericPassword, - kSecAttrService: service, - kSecAttrAccount: account - ] as CFDictionary - let q3 = [ - kSecValueData: d1 - ] as CFDictionary - SecItemUpdate(q2, q3) -} - -// 读取 -let q4 = [ - kSecAttrService: service, - kSecAttrAccount: account, - kSecClass: kSecClassGenericPassword, - kSecReturnData: true -] as CFDictionary - -var re: AnyObject? -SecItemCopyMatching(q4, &re) -guard let reData = re as? Data else { return } -print(String(decoding: reData, as: UTF8.self)) // keyChain github token - -// 删除 -let q5 = [ - kSecAttrService: service, - kSecAttrAccount: account, - kSecClass: kSecClassGenericPassword, -] as CFDictionary - -SecItemDelete(q5) -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/AppIntentTimelineProvider(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/AppIntentTimelineProvider(ap).md" deleted file mode 100644 index ed5a0a470..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/AppIntentTimelineProvider(ap).md" +++ /dev/null @@ -1,37 +0,0 @@ - -AppIntentConfiguration 需要 AppIntentTimelineProvider,AppIntentTimelineProvider 需要实现 `snapshot`、`placeholder` 和 `timeline` 三个方法来确定小组件在展示和实际运行时间线时的视图和数据。 - -```swift -struct ArticleIntentProvider: AppIntentTimelineProvider { - - func snapshot(for configuration: ArticleIntent, in context: Context) async -> ArticleEntry { - return .init( - date: Date(), - author: "snapshot" - ) - } - - func placeholder(in context: Context) -> ArticleEntry { - return .init( - date: Date(), - author: "某人" - ) - } - - func timeline(for configuration: ArticleIntent, in context: Context) async -> Timeline { - return Timeline( - entries: [ - .init(date: Date(), - author: configuration.author, - rate: await ArticleStore().rate())], - policy: .never) - } -} - -struct ArticleEntry: TimelineEntry { - let date: Date - let author: String - var rate: Int = 0 - //... -} -```` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/Widget View(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/Widget View(ap).md" deleted file mode 100644 index a68069f61..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/Widget View(ap).md" +++ /dev/null @@ -1,120 +0,0 @@ - -## 不同的大小设置不同视图 - -```swift -struct ArticleWidgetView: View { - var entry: Provider.Entry - @Environment(\.widgetFamily) var family - - @ViewBuilder - var body: some View { - switch family { - case .systemSmall: - SomeViewSmall() - default: - SomeViewDefault() - } - } -} -``` - -## 锁屏小组件 - -让小组件支持锁屏 - -```swift -struct ArticleWidget: Widget { - - var body: some WidgetConfiguration { - StaticConfiguration( - ... - ) { entry in - ... - } - ... - .supportedFamilies([ - .systemSmall, - .systemMedium, - .systemLarge, - - // 添加支持到 Lock Screen widgets - .accessoryCircular, - .accessoryRectangular, - .accessoryInline, - ]) - } -} -``` - -## 不同类型 widgetFamily 实现不同视图 - -```swift -struct ArticleWidgetView : View { - - let entry: ViewSizeEntry - // 获取 widget family 值 - @Environment(\.widgetFamily) var family - - var body: some View { - switch family { - case .accessoryRectangular: - RectangularWidgetView() - case .accessoryCircular: - CircularWidgetView() - case .accessoryInline: - InlineWidgetView() - default: - ArticleWidgetView(entry: entry) - } - } -} -``` - -## 不同渲染模式实现不同视图 - -小组件有三种不同的渲染模式: - -- Full-color:主屏用 -- Vibrant:用于待机模式和锁屏 -- The accented:用于手表 - -```swift -struct ArticleWidgetView: View { - let entry: Entry - - @Environment(\.widgetRenderingMode) private var renderingMode - - var body: some View { - switch renderingMode { - case .accented: - AccentedWidgetView(entry: entry) - case .fullColor: - FullColorWidgetView(entry: entry) - case .vibrant: - VibrantWidgetView(entry: entry) - default: - DefaultView() - } - } -} -``` - -## 视图交互 - -使用 AppIntent - -```swift -struct ArticleWidgetView : View { - var entry: IntentProvider.Entry - - var body: some View { - VStack(spacing: 20) { - ... - - Button(intent: RunIntent(rate: entry.rate), label: { - ... - }) - } - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\210\267\346\226\260\345\260\217\347\273\204\344\273\266(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\210\267\346\226\260\345\260\217\347\273\204\344\273\266(ap).md" deleted file mode 100644 index 4e9fc3ab2..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\210\267\346\226\260\345\260\217\347\273\204\344\273\266(ap).md" +++ /dev/null @@ -1,87 +0,0 @@ - -## 通过 Text 视图更新 - -倒计时 - -```swift - -let futureDate = Calendar.current.date(byAdding: components, to: Date())! - -// 日期会在 Text 视图中动态变化 - -``` - -```swift -struct CountdownWidgetView: View { - - var body: some View { - Text(futureDate(), style: .timer) - } - - private func futureDate() -> Date { - let components = DateComponents(second: 10) - let futureDate = Calendar.current.date(byAdding: components, to: Date())! - return futureDate - } -} -``` - -## Timeline Provider 更新 - -在 timeline 方法中实现,entries 包含了不同更新的数据。 - -```swift -func timeline(for configuration: ArticleIntent, in context: Context) async -> Timeline { - return Timeline( - entries: [ - .init(date: Date(), - author: configuration.author, - rate: await ArticleStore().rate())], - policy: .never) -} -``` - -## 更新策略 - -3 种类型的刷新策略: -- `atEnd`:上个刷新完成直接进入下个刷新,但是进入下一个刷新的时间由系统决定。 -- `after(Date)`:指定进入下个刷新的时间,但是具体时间还是由系统说了算,因此可以理解为是指定的是最早进入下个刷新的时间。 -- `never`:不会进入下个刷新,除非显式调用 `reloadTimelines(ofKind:)` - -举例,指定下个刷新周期至少是上个周期结束10秒后: - -```swift -let lastUpdateDate = entries.last!.date -let nextUpdateDate = Calendar.current.date(byAdding: DateComponents(second: 10), to: lastUpdate)! - -let timeline = Timeline(entries: entries, policy: .after(nextUpdate)) -``` - -## Relevance 优先级 - -App 自定义刷新 Timeline 的优先级,使用 Relevance。先在 TimelineEntry 里定义: - -```swift -struct ArticleEntry: TimelineEntry { - let date: Date - ... - let relevance: TimelineEntryRelevance? -} -``` - -在 timeline 方法中根据必要刷新程序,定义不同 relevance 的值。 - -## App 主动刷新 - -```swift -// 刷新单个小组件 -WidgetCenter.shared.reloadTimelines(ofKind: "CountryWidget") - -// 刷新所有小组件 -WidgetCenter.shared.reloadAllTimelines() -``` - -## 刷新小组件的最佳实践 - -调试时刷新率不会有限制,生产环境每天最多40到70次,相当于每15到60分钟刷新一次。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-AppIntentConfiguration(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-AppIntentConfiguration(ap).md" deleted file mode 100644 index 3bffa81d6..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-AppIntentConfiguration(ap).md" +++ /dev/null @@ -1,85 +0,0 @@ - - -iOS 17 开始可以使用 AppIntentConfiguration 来配置小组件 - -```swift -import SwiftUI -import WidgetKit -import AppIntents - -struct ArticleWidget: Widget { - var body: some WidgetConfiguration { - AppIntentConfiguration( - kind: "com.starming.articleWidget", - intent: ArticleIntent.self, - provider: ArticleIntentProvider() - ) { entry in - ArticleWidgetView(entry: entry) - } - .configurationDisplayName("Article Widget") - .description("这是一个 Article Widget.") - .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) - } -} - -struct ArticleWidgetView: View { - var entry: IntentProvider.Entry - var body: some View { - Text(entry.author) - } -} - -struct ArticleIntentProvider: AppIntentTimelineProvider { - - func snapshot(for configuration: ArticleIntent, in context: Context) async -> ArticleEntry { - return .init( - date: Date(), - author: "snapshot" - ) - } - - func placeholder(in context: Context) -> ArticleEntry { - return .init( - date: Date(), - author: "某人" - ) - } - - func timeline(for configuration: ArticleIntent, in context: Context) async -> Timeline { - return Timeline( - entries: [ - .init(date: Date(), - author: configuration.author, - rate: await ArticleStore().rate())], - policy: .never) - } -} - -struct ArticleEntry: TimelineEntry { - let date: Date - let author: String - var rate: Int = 0 - //... -} - -// 放在主应用中和小组件交互 -struct ArticleIntent: WidgetConfigurationIntent { - - static var title: LocalizedStringResource = "文章" - var author: String = "某某某" - - func perform() async throws -> some IntentResult { - //... - return .result() - } -} - -class ArticleStore { - //... SwiftData 相关配置 - @MainActor - func rate() async -> Int { - //... 获取 - return 5 - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-Deep link(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-Deep link(ap).md" deleted file mode 100644 index 383e55dda..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-Deep link(ap).md" +++ /dev/null @@ -1,3 +0,0 @@ - -medium 和 large 的小组件可以使用 Link,small 小组件使用 `.widgetURL` 修饰符。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-StaticConfiguration(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-StaticConfiguration(ap).md" deleted file mode 100644 index 850033d72..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-StaticConfiguration(ap).md" +++ /dev/null @@ -1,64 +0,0 @@ - -在 Xcode 中,File -> New -> Target,选择 Widget Extension - -```swift -import WidgetKit -import SwiftUI - -// Timeline Entry -struct ArticleEntry: TimelineEntry { - let date: Date - let title: String -} - -// Widget View -struct ArticleWidgetView : View { - let entry: ArticleEntry - - var body: some View { - Text(entry.title) - } -} - -// Timeline Provider -struct ArticleTimelineProvider: TimelineProvider { - typealias Entry = ArticleEntry - - func placeholder(in context: Context) -> Entry { - // 占位大小,内容不会显示 - return ArticleEntry(date: Date(), title: "Placeholder") - } - - func getSnapshot(in context: Context, completion: @escaping (Entry) -> ()) { - let entry = ArticleEntry(date: Date(), title: "Snapshot") - completion(entry) - } - - func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { - let entry = ArticleEntry(date: Date(), title: "Timeline") - let timeline = Timeline(entries: [entry], policy: .never) - completion(timeline) - } -} - -// Widget Configuration -@main -struct ArticleWidget: Widget { - - var body: some WidgetConfiguration { - StaticConfiguration( - kind: "com.starming.articleWidget", - provider: ArticleTimelineProvider() - ) { entry in - ArticleWidgetView(entry: entry) - } - .configurationDisplayName("Article Widget") - .description("这是一个 Article Widget.") - .supportedFamilies([ - .systemSmall, - .systemMedium, - .systemLarge, - ]) - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" deleted file mode 100644 index 7c5231826..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\345\217\202\350\200\203\350\265\204\346\226\231(ap).md" +++ /dev/null @@ -1,42 +0,0 @@ - - -- [Complications and widgets: Reloaded - WWDC22 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2022/10050/) -- [WidgetKit 官方](https://developer.apple.com/documentation/WidgetKit) -- 官方指南 [Creating Lock Screen Widgets and Watch Complications](https://developer.apple.com/documentation/WidgetKit/Creating-lock-screen-widgets-and-watch-complications) -- [WidgetKit 主题](https://developer.apple.com/widgets/) -- [Debugging Widgets](https://developer.apple.com/documentation/widgetkit/debugging-widgets) -- [WidgetKit Session](https://developer.apple.com/videos/all-videos/?q=WidgetKit) -- [Complications and widgets: Reloaded - WWDC22 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2022/10050) - -WWDC -- 介绍怎么将 widgets 添加到 lock screen 的 session [Complications and widgets: Reloaded](https://developer.apple.com/videos/play/wwdc2022-10050)。对应的实例代码 [Adding widgets to the Lock Screen and watch faces](https://developer.apple.com/documentation/widgetkit/adding_widgets_to_the_lock_screen_and_watch_faces) - - -23 -- [Bring widgets to life - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10028) -- [Build widgets for the Smart Stack on Apple Watch - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10029) -- [Bring widgets to new places - WWDC23 - Videos - Apple Developer](https://developer.apple.com/wwdc23/10027) - -22 -- [Go further with Complications in WidgetKit - WWDC22 - Videos - Apple Developer](https://developer.apple.com/wwdc22/10051) 进一步了解 WidgetKit 中的复杂功能 -- [Challenge: WidgetKit workshop - Discover - Apple Developer](https://developer.apple.com/news/?id=2q8t97ob) 挑战:WidgetKit 研讨会 -- [Complications and widgets: Reloaded - WWDC22 - Videos - Apple Developer](https://developer.apple.com/wwdc22/10050) 复杂功能和小组件:重新载入 -- [Go further with Complications in WidgetKit - WWDC22 - Videos - Apple Developer](https://developer.apple.com/wwdc22/10051) 进一步了解 WidgetKit 中的复杂功能 - -21 -- [Principles of great widgets - WWDC21 - Videos - Apple Developer](https://developer.apple.com/wwdc21/10048) 优秀小组件的原则 -- [Add intelligence to your widgets - WWDC21 - Videos - Apple Developer](https://developer.apple.com/wwdc21/10049) 让您的小组件更加智能 -- [Documentation Spotlight: Dynamically display timely data in your widgets - Discover - Apple Developer](https://developer.apple.com/news/?id=zawi5f1g) 聚焦文档:在小组件中适时动态显示数据 - - -20 -- [Build SwiftUI views for widgets - WWDC20 - Videos - Apple Developer](https://developer.apple.com/wwdc20/10033) 为小组件构建 SwiftUI 视图。中文:[WWDC20 - Build SwiftUI views for widgets - 小专栏](https://xiaozhuanlan.com/topic/3691507248) -- [Widgets code-along - Discover - Apple Developer](https://developer.apple.com/news/?id=yv6so7ie) 小组件编写临摹课程 -- [Widgets Code-along, part 1: The adventure begins - WWDC20 - Videos - Apple Developer](https://developer.apple.com/wwdc20/10034) 小组件编程临摹课程1:开始学习 -- [Widgets Code-along, part 2: Alternate timelines - WWDC20 - Videos - Apple Developer](https://developer.apple.com/wwdc20/10035) 小组件编程临摹课程2:变更时间线 -- [Widgets Code-along, part 3: Advancing timelines - WWDC20 - Videos - Apple Developer](https://developer.apple.com/wwdc20/10036) 小组件编程临摹课程3:加速时间线 -- [Meet WidgetKit - WWDC20 - Videos - Apple Developer](https://developer.apple.com/wwdc20/10028) 为你介绍 WidgetKit -- [Widgets code-along - Discover - Apple Developer](https://developer.apple.com/news/?id=yv6so7ie) 小组件编程临摹课程 -- [Add configuration and intelligence to your widgets - WWDC20 - Videos - Apple Developer](https://developer.apple.com/wwdc20/10194) 为小组件添加配置和智能 -- [Meet WidgetKit - WWDC20 - Videos - Apple Developer](https://developer.apple.com/wwdc20/10028) 为你介绍 WidgetKit - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\350\216\267\345\217\226\344\275\215\347\275\256\346\235\203\351\231\220\346\233\264\346\226\260\345\206\205\345\256\271(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\350\216\267\345\217\226\344\275\215\347\275\256\346\235\203\351\231\220\346\233\264\346\226\260\345\206\205\345\256\271(ap).md" deleted file mode 100644 index f3c437155..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\350\216\267\345\217\226\344\275\215\347\275\256\346\235\203\351\231\220\346\233\264\346\226\260\345\206\205\345\256\271(ap).md" +++ /dev/null @@ -1,7 +0,0 @@ - -小组件获取位置权限和主应用 target 里获取方式很类似,步骤: - -- 在 info 里添加 `NSWidgetUseLocation = ture`。 -- 使用 CLLocationManager 来获取位置信息,设置较低的精度。 -- 用 `isAuthorizedForWidgetUpdates` 请求位置权限。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\350\277\234\347\250\213\345\256\232\346\227\266\350\216\267\345\217\226\346\225\260\346\215\256(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\350\277\234\347\250\213\345\256\232\346\227\266\350\216\267\345\217\226\346\225\260\346\215\256(ap).md" deleted file mode 100644 index da1d6f5fb..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\350\277\234\347\250\213\345\256\232\346\227\266\350\216\267\345\217\226\346\225\260\346\215\256(ap).md" +++ /dev/null @@ -1,17 +0,0 @@ - -在 TimelineProvider 中的 timeline 方法中加入请求逻辑 - -```swift -func timeline(for configuration: RunIntent, in context: Context) -> Void) async -> Timeline { - guard let article = try? await ArticleFetch.fetchNewestArticle() else { - return - } - let entry = ArticleEntry(date: Date(), article: article) - - // 下次在 30 分钟后再请求 - let afterDate = Calendar.current.date(byAdding: DateComponents(minute: 30), to: Date())! - return Timeline(entries: [entry], policy: .after(afterDate)) -} -``` - - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\351\205\215\347\275\256\351\200\211\351\241\271(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\351\205\215\347\275\256\351\200\211\351\241\271(ap).md" deleted file mode 100644 index 67896302d..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266-\351\205\215\347\275\256\351\200\211\351\241\271(ap).md" +++ /dev/null @@ -1,75 +0,0 @@ - -## 显示区域 - -iOS 17 新增显示区域配置,有四种 - -- homeScreen -- lockScreen -- standBy -- iPhoneWidgetsOnMac - -设置小组件不在哪个区域显示某尺寸。 - -```swift -struct SomeWidget: Widget { - ... - var body: some WidgetConfiguration { - AppIntentConfiguration( - ... { entry in - ... - } - // 在 StandBy 中取消显示 systemSmall 尺寸 - .disfavoredLocations([.standBy], for: [.systemSmall]) - } -} -``` - -## 取消内容边距 - -使用 `.contentMarginsDisabled()` 取消内容边距。 - -```swift -struct SomeWidget: Widget { - ... - var body: some WidgetConfiguration { - AppIntentConfiguration( - ... { entry in - ... - } - // 使 Content margin 失效 - .contentMarginsDisabled() - } -} -``` - -每个平台内容边距大小不同,环境变量 `\.widgetContentMargins` 可以读取内容边距的大小。 - -## 取消背景删除 - -在 StandBy 和 LockScreen 的某些情况,小组件的背景是会被自动删除的。 - -使用 `containerBackgroundRemovable()` 修饰符可以取消背景删除。 - -```swift -struct SomeWidget: Widget { - ... - var body: some WidgetConfiguration { - AppIntentConfiguration( - ... { entry in - ... - } - // 取消背景删除 - .containerBackgroundRemovable(false) - // 让自己的背景可以全覆盖 - .contentMarginsDisabled() - } -} -``` - -## 后台网络处理 - -```swift -.onBackgroundURLSessionEvents { (identifier, completion) in - //... -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266\345\212\250\347\224\273(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266\345\212\250\347\224\273(ap).md" deleted file mode 100644 index 672211388..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266\345\212\250\347\224\273(ap).md" +++ /dev/null @@ -1,22 +0,0 @@ - -## Text 视图动态时间 - -利用 Text 的动态时间能力 - -## timeline 动画 - -timeline 是由一组时间和数据组成的,每次刷新时,小组件通过和上次数据不一致加入动画效果。 - -默认情况小组件使用的是弹簧动画。我们也可以添加转场(Transition)、动画(Animation)和内容过渡(Content Transition)动画效果。 - -## 文本内容过渡动画效果 - -```swift -.contentTransition(.numericText(value: rate)) -``` - -## 从底部翻上来的专场 - -```swift -.transition(.push(from: .bottom)) -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266\350\256\277\351\227\256SwiftData(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266\350\256\277\351\227\256SwiftData(ap).md" deleted file mode 100644 index fdcb4115c..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\345\260\217\347\273\204\344\273\266\350\256\277\351\227\256SwiftData(ap).md" +++ /dev/null @@ -1,7 +0,0 @@ - -Wdiget target 访问主应用 target 的 SwiftData 数据步骤如下: - -- 对主应用和 Widget 的 target 中的 Signing & Capabilities 都添加 App Groups,并创建一个新组,名字相同。 -- SwiftData 的模型同时在主应用和 Widget 的 target 中。 -- StaticConfiguration 或 AppIntentConfiguration 中添加 `modelContainer()` 修饰符,让 SwiftData 的容器可用。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\346\224\257\346\214\201\345\244\232\344\270\252\345\260\217\347\273\204\344\273\266(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\346\224\257\346\214\201\345\244\232\344\270\252\345\260\217\347\273\204\344\273\266(ap).md" deleted file mode 100644 index 27ad39cbb..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\346\224\257\346\214\201\345\244\232\344\270\252\345\260\217\347\273\204\344\273\266(ap).md" +++ /dev/null @@ -1,27 +0,0 @@ - - -widget bundle 可以支持多个小组件。 - -```swift -@main -struct FirstWidgetBundle: WidgetBundle { - - @WidgetBundleBuilder - var body: some Widget { - FirstWidget() - SecondWidget() - ... - SecondWidgetBundle().body - } -} - -struct SecondWidgetBundle: WidgetBundle { - - @WidgetBundleBuilder - var body: some Widget { - SomeWidgetOne() - SomeWidgetTwo() - ... - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\350\216\267\345\217\226\345\260\217\347\273\204\344\273\266\345\275\242\347\212\266(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\350\216\267\345\217\226\345\260\217\347\273\204\344\273\266\345\275\242\347\212\266(ap).md" deleted file mode 100644 index 44942a919..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\260\217\347\273\204\344\273\266/\350\216\267\345\217\226\345\260\217\347\273\204\344\273\266\345\275\242\347\212\266(ap).md" +++ /dev/null @@ -1,15 +0,0 @@ - -不同设备小组件大小和形状都不同,比如要加个边框,就很困难。这就需要使用 `ContainerRelativeShape` 来获取 Shape 视图容器。 - -```swift -var body: some View { - ZStack { - ContainerRelativeShape() - .inset(by: 2) - .fill(.pink) - Text("Hello world") - ... - } -} -``` - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\267\245\347\250\213\346\250\241\345\274\217/\345\215\225\344\276\213(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\267\245\347\250\213\346\250\241\345\274\217/\345\215\225\344\276\213(ap).md" deleted file mode 100644 index 9fcb9855f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\267\245\347\250\213\346\250\241\345\274\217/\345\215\225\344\276\213(ap).md" +++ /dev/null @@ -1,8 +0,0 @@ -```swift -struct S { - static let shared = S() - private init() { - // 防止实例初始化 - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\345\267\245\347\250\213\346\250\241\345\274\217/\347\250\213\345\272\217\345\205\245\345\217\243\347\202\271(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\345\267\245\347\250\213\346\250\241\345\274\217/\347\250\213\345\272\217\345\205\245\345\217\243\347\202\271(ap).md" deleted file mode 100644 index d75960955..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\345\267\245\347\250\213\346\250\241\345\274\217/\347\250\213\345\272\217\345\205\245\345\217\243\347\202\271(ap).md" +++ /dev/null @@ -1,28 +0,0 @@ -Swift 允许全局编写 Swift 代码,实际上 clang 会自动将代码包进一个模拟 C 的函数中。Swift 也能够指定入口点,比如 @UIApplicationMain 或 @NSApplicationMain,UIKit 启动后生命周期管理是 AppDelegate 和 SceneDelegate,《 [Understanding the iOS 13 Scene Delegate](https://www.donnywals.com/understanding-the-ios-13-scene-delegate/) 》这篇有详细介绍。 - -@UIApplicationMain 和 @NSApplicationMain 会自动生成入口点。这些入口点都是平台相关的,Swift 发展来看是多平台的,这样在 Swift 5.3 时引入了 @main,可以方便的指定入口点。代码如下: -```swift -@main // 要定义个静态的 main 函数 -struct M { - static func main() { - print("let's begin") - } -} -``` - - [ArgumentParser](https://github.com/apple/swift-argument-parser) 库,Swift 官方开源的一个开发命令行工具的库,也支持 @main。使用方法如下: -```swift -import ArgumentParser - -@main -struct C: ParsableCommand { - @Argument(help: "Start") - var phrase: String - - func run() throws { - for _ in 1...5 { - print(phrase) - } - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\345\206\205\345\255\230\347\256\241\347\220\206(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\345\206\205\345\255\230\347\256\241\347\220\206(ap).md" deleted file mode 100644 index c7f3d95f3..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\345\206\205\345\255\230\347\256\241\347\220\206(ap).md" +++ /dev/null @@ -1,3 +0,0 @@ -相关提案包括 [SE-0349 Unaligned Loads and Stores from Raw Memory](https://github.com/apple/swift-evolution/blob/main/proposals/0349-unaligned-loads-and-stores.md) 、[SE-0334 Pointer API Usability Improvements](https://github.com/apple/swift-evolution/blob/main/proposals/0334-pointer-usability-improvements.md) 、[SE-0333 Expand usability of withMemoryRebound](https://github.com/apple/swift-evolution/blob/main/proposals/0333-with-memory-rebound.md) - -Set 使用新的 Temporary Buffers 功能,让 intersect 速度提升了 4 到 6 倍。 diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\346\200\247\350\203\275\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\346\200\247\350\203\275\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" deleted file mode 100644 index 82463b17f..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\346\200\247\350\203\275\346\212\200\346\234\257\346\274\224\350\277\233(ap).md" +++ /dev/null @@ -1,28 +0,0 @@ -[Improve app size and runtime performance](https://developer.apple.com/videos/play/wwdc2022/110363) - -今年苹果通过更有效的检查 Swift 协议,使 OC 消息发送调用更小,使 autorelease elision 更快更小这几个个方面来让 App 体积更小,性能更高。 - -Swift 协议检查。 - -一个协议通过 as 操作符检查传递值是否符合协议,这种检查会在编译器的构建时间被优化掉,所以往往需要在运行时借助之前计算协议检查元数据来看对象是否真的符合了协议。一些元数据是在编译时建的,但还有很多元数据只能在启动时建立,特别是使用泛型时。协议多了,会增加耗时,差不多会多一半启动时间。 - -今年 Apple 推出新的 Swift 运行时,可以提前计算 Swift 协议元数据,作为 App 可执行文件和它在启动时使用的任何动态库的 dyld 闭包的一部分。这个是在系统上的,因此,只要是使用了今年最新系统的 App 都会享受这个优化,可以理解为,新系统上启动老 App 也会快些。 - -消息发送。 - -Xcode 14 中新的编译器和链接器已经将 ARM64 的消息发送调用从 12 字节减少到 8 字节。因此如果你的 App 都是 OC 代码的话,使用 Xcode 14 编出来的二进制文件可以少 2%。老系统也有效。 - -使用 objc_stubs_small 选项可以只优化大小,获得最大的大小优化。objc_msgSend 调动有 8 个字节指令,也就是2个指令是专门用来准备 selector 的,对于任何特定的 selector,总是相同的代码,由于始终是相同的代码,那么就可以对其共享,每个 selector 只 emit 一次,而不是每次发送消息时都 emit。共享这段代码地方是一个叫 selector stub 的函数。 - -ARC 会在编译器插入大量的 c 的 retain/release 函数调用。这些调用遵守平台应用二进制接口(ABI)所定义的 c 语言 call convention。也就意味着我们要更多代码来完成这些调用,用来传递正确寄存器的指针。Apple 今年推出了自定义的 call convention 根据指针位置,适时使用正确变量而不用移动它,从而摆脱了调用里的多余代码。Apple 果然是坚持用户体验优先,为了更好体验不惜修改 c 的 ABI。 - -autorelease elision 。 - -App 今年对 objc 运行时进行了修改,使 autorelease elision 更小更快。deployment target 为 iOS 16 今年新系统时才可享用哦。 - -Apple 怎么做的呢? - -ARC 在调用方插入一个 retain,在被调用的函数中插入一个 release。当我们返回我们的临时对象时,我们需要在函数中先释放它,因为它要离开 scope。在它还没有任何其它引用时还不能这么做,不然返回前他就会被销毁。Apple 现在使用一个新的 convention ,让其可以返回临时对象。做法是当返回一个自动释放值,编译器会发出一个特殊标记,这个标记会告诉运行时这是符合自动释放条件的。它的后面是 retain,我们会在后面执行。获取返回地址,也就是一个指针,将它先保存起来,然后离开运行时的自动释放调用。在运行时,可以将保留时得到的指正和先前做自动释放时保存的指针进行比较,这样标记指令不再是数据之间的比较,比较指针内存访问少。比较成功就可以省去 autorelease/retain。 - -autorelease elision 的优化同样也可以减少 2% 大小。感谢 Apple 为了用户和开发者 OKR 的付出。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\350\260\203\350\257\225(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\350\260\203\350\257\225(ap).md" deleted file mode 100644 index f1006b108..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\350\260\203\350\257\225(ap).md" +++ /dev/null @@ -1,10 +0,0 @@ -session [Debug Swift debugging with LLDB](https://developer.apple.com/videos/play/wwdc2022-110370) - -编译器编译 swift 文件生成 `.o` 文件会有 `__debug_info` 段,其中有可以映射到源文件和行号的地址。debug 信息可以链接到 `.dSYM` 包。debug 信息链接器叫 dsymutil,dsymutil 可以为每个动态库、framework 或 dylib 和可执行文件打包一个 debug 信息存档(`.dSYM` 包)。 - -image 和路径怎么重映射。使用 `image list nameOfFramework` 来检查 LLDB 是否找到了我们应用程序里嵌入的第三方框架的 debug dSYM。使用 `image lookup 0xMemoryAddressHere` 获取当前地址更多信息。要重新映射源文件 `.dSYM` 路径,使用 `settings set target.source-map old/path new/path`。每个 `.dSYM` 都有一个 `UUID.plist`,我们可以在其中设置 DBGSourcePathRemapping 这个字典。 - -Xcode 14 新增 `swift-healthcheck` 命令,这个命令可以了解 module 为何导入失败。 - -LLDB 怎么找到 Swift module?每个 `.dSYM` 包都可以包含二级制 swift module,其中可能包含桥头文件、swift 接口文件 `.swiftinterface`,还有 debug 信息。静态存档不是由链接器生成的,需要向链接器注册 swift module,使用 `ld ... -add-ast-path /path/to/My.swiftmodule` ,动态库和可执行文件的话,Xcode 会自动完成此操作。可以使用 dsymutil 来 dump 你可执行文件的符号表,并用 grep 找 swiftmodule,命令是 `dsymutil -s MyApp | grep .swiftmodule` 。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\351\223\276\346\216\245\345\231\250(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\351\223\276\346\216\245\345\231\250(ap).md" deleted file mode 100644 index 22d0da183..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\346\200\247\350\203\275\345\222\214\346\236\204\345\273\272/\351\223\276\346\216\245\345\231\250(ap).md" +++ /dev/null @@ -1,38 +0,0 @@ -[Link fast: Improve build and launch time](https://developer.apple.com/videos/play/wwdc2022/110362/) 详细讲了 Apple 今年怎么改进了 link,思路很棒,很值得学习。 - -Static linking 和 Dynamic linking ,也就是静态链接和动态链接。 - -静态链接就是链接各个编译好的源文件以及链接源文件和编译好的库文件,通过将函数名放到符号表,链接新文件时确定先前是否有包含的 undefined 符号,给函数的数据指令分配地址,最后生成一个有 TEXT、DATA、LINKEDIT 段的可执行文件。 - -今年 Apple 通过利用多核优势让静态链接快了两倍。 - -具体做法是,并行的拷贝文件内容。并行构建 LINKEDIT 段的各个不同部分。并行改变 UUID 计算和 codesigning 哈希。然后是提高 exports-trie 构建器的算法。使用最新的 Crypto 库利用硬件加速的优势加速 UUID 计算。提高其它静态库处理算法库,debug-notes 生成也更快了。 - -Apple 推荐静态库最佳实践是: - -使用 `-all_load` 或 `-force_load` 可以让 .a 文件像 .o 文件那样并行处理,不过开启这个选项需要先处理重复的符号。另外一个副作用是会将一些被判断无用的代码也被链接进来,使包体变大,因此开启之前可以先使用静态分析工具分析处理,这个过程定期做就行,不用放到每次编译过程中。演讲者推荐使用 `-dead_strip` 选项,但是这样做并没有真实去掉费代码,以后这些代码还是会被编译分析,如果只是暂时不用,可以先注释掉。 - -使用 `-no_exported_symbols` 选项。链接器生成的 LINKEDIT 段的一部分是 exports trie,这是一个前缀树,对所有导出的符号名称、地址和标志进行编码。动态库 是会导出符号的,但运行的二进制文件其实是不用这些符号的,因此可以用 `-no_exported_symbols` 选项来跳过 LINKEDIT 中 trie 数据结构的创建,这样链接起来就快多了。如果程序导出符号是一百万个,这个选项就可以减少 2 到 3 秒的时间。但需要注意的是,如果要加载插件链接回主程序就需要所有的导出的 trie 数据,无法用这个选项。 - -另外一个是 `-no_deduplicate` 选项。先前 Apple 给链接器加了个 pass 用来合并函数的指令相同,函数名不相同,这个 pass 会对每个函数的指令进行递归散列,用这种方式来找重复指令,这样做比较费 CPU,由于调试时其实是不需要关注包大小,因此可以加上 `-no_deduplicate` 选项来跳过这个 pass。 - -这些选项在 Xcode 的 Other Linker Flags 里进行设置即可。 - -动态库也就是 dylib,其它平台就是 DSO 或 DLL。 动态链接器不是将代码从库里考到主二进制里,而是记录某种承诺,记录从动态库中使用符号名称,还有库路径。这样做好处就是好复用动态库,不用拷贝多份。虚拟内存看到多进程使用相同动态库,就会重新给这个动态库用相同的物理内存页。 - -动态库好处是构建快了,启动加载慢了,多个动态库不光要加载,还要在启动时链接。也就是把链接成本从本地构建换到了用户启动时。动态库还有个缺点是基于动态库的程序会有更多的 dirty 页,因为静态链接时会把全局数据放到主程序同一个 DATA 页中,动态库的话,每个都在自己的 DATA 页中。 - -动态库工作的原理是,可执行的二进制会有不同权限的段,至少会有 TEXT、DATA 和 LINKEDIT。分段总是操作系统页大小的倍数。TEXT 段有执行的权限,CPU 可以将页上的字节当做机器代码指令。运行时,dyld 会根据每个段权限将可执行文件 mmap() 到内存,这些段是页大小和页对齐的,虚拟内存系统可以直接将程序或动态库文件设置为 VM 范围的备份存储。在这些页的内存访问前是不会被加载到 RAM 里,就会触发一个页 fault,导致 VM 去读取文件的子范围,将内存填充到需要 RAM 页中。光映射不够,还要用某种方式“wired up”或绑到动态库上。比如要调用动态库上的某个函数,会转换成调用 site,调用 site 成为一个在相同 TEXT 段合成的 sub 的调用,相对地址在构建时就知道了,就意味着可以正确的形成 BL 指令。这样做的好处是,stub 从 DATA 加载一个指针并跳到对应的位置,不用在运行时修改 TEXT 段,dyld 只在运行时改 DATA 段。dyld 所进行的修改很简单,就是在 DATA 段里设置了一个指针而已。 - -当 dyld 或应用程序的指针指向自己时要 rebase,ASLR 使 dyld 以随机地址加载动态库,内部指针不能在构建时设置,dyld 在启动时 rebase 这些指针,磁盘上,如果动态库在地址零出被加载,这些指针包含它们的目标地址。LINKEDIT 需要记录的就是每个重定位的位置。然后,dyld 只需将动态库的实际加载地址添加到每个 rebase 位置。还有种修改方式是绑定,绑定就是符号引用,符号存储在 LINKEDIT 中,dyld 在动态库的 exports tire 中找实际地址,然后 dyld 将该值存储在绑定指定的位置。 - -今年 Apple 发布了一个新的修改方式 chained fixups。较前面两种的优势就是可以使 LINKEDIT 更小。新格式只存储每个 DATA 页中第一个 fixup 位置和一个导入的符号列表。其它信息编码到 DATA 段。iOS 13.4 就开始支持了。 - -下面先说下 dyld 原理介绍。 - -dyld 从主可执行文件开始,解析 mach-o 找依赖动态库,对动态库进行 mmap()。然后对每个动态库进行遍历并解析 mach-o 结构,根据需要加载其它动态库。加载完毕,dyld 会查找所有需要绑定符号,并在修改时使用这些地址。最后修改完,dyld 自下而上运行初始化程序。先前做的优化是只要程序和动态库,dyld 很多步骤都可以在首次启动时被缓存。 - -今年 Apple 做了更多的优化,这个优化叫 page-in linking,就是 dyld 在启动时做的 DATA 页面修改放到 page-in 时,也可以理解为懒修改。以前,在 mmap() 区域的某些页面中第一次使用某些地址会触发内核读入该页面。现在如果它是一个数据页,内核会应用改页需要的修改。这种机制减少了 dirty 内存和启动时间。意味着 DATA_CONST 也是干净的,可以像 TEXT 页一样被 evicted 和重新创建,以减少内存压力。需要注意的是 page-in linking 只用于启动,dlopen() 不支持。你看,Apple 优化启动的思路也是按需加载。 - -Apple 还提供了追踪 dyld 运行情况的 dyld_usage 工具。检查磁盘和 dyld 缓存中的二进制文件的 dyld_info 工具。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\347\263\273\347\273\237\350\203\275\345\212\233/AppIcon(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\347\263\273\347\273\237\350\203\275\345\212\233/AppIcon(ap).md" deleted file mode 100644 index 4eeeff179..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\347\263\273\347\273\237\350\203\275\345\212\233/AppIcon(ap).md" +++ /dev/null @@ -1,37 +0,0 @@ - -下面代码显示 App 图标和版本号: - -```swift -struct ContentView: View { - var body: some View { - VStack { - #if os(macOS) - if let appIcon = NSImage(named: "AppIcon") { - Image(nsImage: appIcon) - .resizable() - .scaledToFit() - .frame(width: 120, height: 120) - } - #elseif os(iOS) - if let appIcon = UIImage(named: "AppIcon") { - Image(uiImage: appIcon) - .resizable() - .scaledToFit() - .frame(width: 120, height: 120) - } - #endif - Text("应用版本: \(AppVersionProvider.appVersion())") - } - .padding() - } -} - -struct AppVersionProvider { - static func appVersion(in bundle: Bundle = .main) -> String { - guard let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { - fatalError("CFBundleShortVersionString should not be missing from info dictionary") - } - return version - } -} -``` diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\347\263\273\347\273\237\350\203\275\345\212\233/Swift-DocC(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\347\263\273\347\273\237\350\203\275\345\212\233/Swift-DocC(ap).md" deleted file mode 100644 index ea4f88a8d..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\347\263\273\347\273\237\350\203\275\345\212\233/Swift-DocC(ap).md" +++ /dev/null @@ -1,8 +0,0 @@ -现在支持 Swift、OC 和 C,文档标记一样。`.doccarchive` 包含可部署的网站内容,兼容大多数托管服务,比如 Github pages。部署到在线服务上可参考 [Generating Documentation for Hosting Online](https://apple.github.io/swift-docc-plugin/documentation/swiftdoccplugin/generating-documentation-for-hosting-online/) 和 [Publishing to GitHub Pages](https://apple.github.io/swift-docc-plugin/documentation/swiftdoccplugin/publishing-to-github-pages/) 文档。 - -和 SPM 集成参看 [SwiftDocCPlugin](https://apple.github.io/swift-docc-plugin/documentation/swiftdoccplugin/) 。 - -session 有 [What’s new in Swift -DocC](https://developer.apple.com/videos/play/wwdc2022-110368) 和 [Improve the discoverability of your Swift-DocC content](https://developer.apple.com/videos/play/wwdc2022-110369) - -[SE-0356 Swift Snippets](https://github.com/apple/swift-evolution/blob/main/proposals/0356-swift-snippets.md) 代码片段用于示例文档的提案。 - diff --git "a/SwiftPamphletApp/Resource/Guide/appstore/\347\275\221\347\273\234/\347\275\221\347\273\234\347\212\266\346\200\201\346\243\200\346\237\245(ap).md" "b/SwiftPamphletApp/Resource/Guide/appstore/\347\275\221\347\273\234/\347\275\221\347\273\234\347\212\266\346\200\201\346\243\200\346\237\245(ap).md" deleted file mode 100644 index 17224cc88..000000000 --- "a/SwiftPamphletApp/Resource/Guide/appstore/\347\275\221\347\273\234/\347\275\221\347\273\234\347\212\266\346\200\201\346\243\200\346\237\245(ap).md" +++ /dev/null @@ -1,75 +0,0 @@ -通过 Network 库的 NWPathMonitor 来检查 - -```swift -import Combine -import Network - -// 网络状态检查 network state check -final class Nsck: ObservableObject { - static let shared = Nsck() - private(set) lazy var pb = mkpb() - @Published private(set) var pt: NWPath - - private let monitor: NWPathMonitor - private lazy var sj = CurrentValueSubject(monitor.currentPath) - private var sb: AnyCancellable? - - init() { - monitor = NWPathMonitor() - pt = monitor.currentPath - monitor.pathUpdateHandler = { [weak self] path in - self?.pt = path - self?.sj.send(path) - } - monitor.start(queue: DispatchQueue.global()) - } - - deinit { - monitor.cancel() - sj.send(completion: .finished) - } - - private func mkpb() -> AnyPublisher { - return sj.eraseToAnyPublisher() - } -} -``` - -使用方法 - -```swift -var sb = Set() -var alertMsg = "" - -Nsck.shared.pb - .sink { _ in - // - } receiveValue: { path in - alertMsg = path.debugDescription - switch path.status { - case .satisfied: - alertMsg = "" - case .unsatisfied: - alertMsg = "😱" - case .requiresConnection: - alertMsg = "🥱" - @unknown default: - alertMsg = "🤔" - } - if path.status == .unsatisfied { - switch path.unsatisfiedReason { - case .notAvailable: - alertMsg += "网络不可用" - case .cellularDenied: - alertMsg += "蜂窝网不可用" - case .wifiDenied: - alertMsg += "Wifi不可用" - case .localNetworkDenied: - alertMsg += "网线不可用" - @unknown default: - alertMsg += "网络不可用" - } - } - } - .store(in: &sb) -```