├── .gitignore
├── README.md
├── Tuist
├── Config.swift
├── Dependencies.swift
├── ProjectDescriptionHelpers
│ ├── Project+Env.swift
│ ├── Project+InfoPlist.swift
│ ├── Project+Templates.swift
│ └── Project+uFeature.swift
└── Templates
│ └── framework
│ ├── framework.swift
│ └── project.stencil
├── Workspace.swift
└── modules
├── App
├── Derived
│ └── InfoPlists
│ │ └── ExampleApp-Info.plist
├── Project.swift
└── src
│ ├── App.swift
│ └── MainView.swift
├── Common
├── Derived
│ └── InfoPlists
│ │ └── Common-Info.plist
├── Project.swift
└── src
│ └── Common.swift
├── RandomProvider
├── Derived
│ └── InfoPlists
│ │ ├── RandomProvider-Info.plist
│ │ └── RandomProviderInterface-Info.plist
├── Project.swift
├── interface
│ └── NumberProvider.swift
└── src
│ ├── NumberProviderRandom.swift
│ └── NumberProviderZero.swift
└── RandomScreen
├── Derived
└── InfoPlists
│ ├── RandomScreen-Info.plist
│ └── RandomScreenInterface-Info.plist
├── Project.swift
├── interface
└── RandomScreen.swift
└── src
├── RandomScreenBig.swift
└── RandomScreenSimple.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | *.xcworkspace
2 | *.xcodeproj
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TuistExample
2 |
3 | Example for using Tuist with modular app architecture. Check out the articles!
4 |
5 | - [iOS App As a Microservice. Modularize Your App With Tuist](https://alexdremov.me/ios-app-as-a-microservice-modularize-your-app-with-tuist/)
6 | - [iOS App As a Microservice. Build Robust App Architecture](https://alexdremov.me/ios-app-as-a-microservice-build-robust-app-architecture/)
7 |
8 | ## Graph
9 | 
10 |
11 | ## Visuals
12 |
13 | https://user-images.githubusercontent.com/25539425/194519593-14910e2a-aef3-453a-9741-055ac1d6fde4.mp4
14 |
--------------------------------------------------------------------------------
/Tuist/Config.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | let config = Config(
4 | plugins: [
5 | .git(url: "https://github.com/tuist/tuist-plugin-lint", tag: "0.3.0")
6 | ]
7 | )
8 |
--------------------------------------------------------------------------------
/Tuist/Dependencies.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 | import ProjectDescriptionHelpers
3 |
4 | let spmDeps = SwiftPackageManagerDependencies(
5 | [
6 | .remote(url: "https://github.com/AlexRoar/FoggyColors",
7 | requirement: .branch("main")),
8 | .remote(url: "https://github.com/apple/swift-async-algorithms",
9 | requirement: .branch("main"))
10 | ],
11 | productTypes: [
12 | "FoggyColors": ProjectDescriptionHelpers.defaultPackageType,
13 | "AsyncAlgorithms": ProjectDescriptionHelpers.defaultPackageType
14 | ]
15 | )
16 |
17 | let dependencies = Dependencies(
18 | swiftPackageManager: spmDeps,
19 | platforms: [
20 | .iOS
21 | ]
22 | )
23 |
--------------------------------------------------------------------------------
/Tuist/ProjectDescriptionHelpers/Project+Env.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | public enum BuildType {
4 | case debug
5 | case release
6 | }
7 |
8 | public let buildType: BuildType = {
9 | Environment.buildTypeRelease.getBoolean(default: false) ? .release : .debug
10 | }()
11 |
12 | public var defaultPackageType: ProjectDescription.Product = {
13 | buildType == .release ? .staticFramework : .framework
14 | }()
15 |
--------------------------------------------------------------------------------
/Tuist/ProjectDescriptionHelpers/Project+InfoPlist.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Project+InfoPlist.swift
3 | // ProjectDescriptionHelpers
4 | //
5 | // Created by Alex Dremov on 23.08.2022.
6 | //
7 |
8 | import ProjectDescription
9 |
10 | public let infoPlistExtension: [_: InfoPlist.Value] = [
11 | "CFBundleShortVersionString": "1.0",
12 | "CFBundleVersion": "1",
13 | "UIMainStoryboardFile": "",
14 | "UILaunchStoryboardName": "",
15 | "NSHealthShareUsageDescription":
16 | .string("Your health and workout records will be used localy for visualizing and analyzing your data"),
17 | "NSHealthUpdateUsageDescription":
18 | .string("Your health and workout records will be used localy for visualizing and analyzing your data"),
19 | "NSHealthRecordsUsageDescription":
20 | .string("Your health and workout records will be used localy for visualizing and analyzing your data")
21 | ]
22 |
--------------------------------------------------------------------------------
/Tuist/ProjectDescriptionHelpers/Project+Templates.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | private let rootPackagesName = "com.exampleproject."
4 |
5 | private func makeBundleID(with addition: String) -> String {
6 | (rootPackagesName + addition).lowercased()
7 | }
8 |
9 | public extension Target {
10 | static func makeApp(
11 | name: String,
12 | sources: ProjectDescription.SourceFilesList,
13 | dependencies: [ProjectDescription.TargetDependency]
14 | ) -> Target {
15 | Target(
16 | name: name,
17 | platform: .iOS,
18 | product: .app,
19 | bundleId: makeBundleID(with: "app"),
20 | deploymentTarget: .iOS(targetVersion: "16.0", devices: .iphone),
21 | infoPlist: .extendingDefault(with: infoPlistExtension),
22 | sources: sources,
23 | dependencies: dependencies
24 | )
25 | }
26 |
27 | static func makeFramework(
28 | name: String,
29 | sources: ProjectDescription.SourceFilesList,
30 | dependencies: [ProjectDescription.TargetDependency] = [],
31 | resources: ProjectDescription.ResourceFileElements? = []
32 | ) -> Target {
33 | Target(
34 | name: name,
35 | platform: .iOS,
36 | product: defaultPackageType,
37 | bundleId: makeBundleID(with: name + ".framework"),
38 | sources: sources,
39 | resources: resources,
40 | dependencies: dependencies
41 | )
42 | }
43 |
44 | private static func feature(
45 | implementation featureName: String,
46 | dependencies: [ProjectDescription.TargetDependency] = [],
47 | resources: ProjectDescription.ResourceFileElements? = []
48 | ) -> Target {
49 | .makeFramework(
50 | name: featureName,
51 | sources: [ "src/**" ],
52 | dependencies: dependencies,
53 | resources: resources
54 | )
55 | }
56 |
57 | private static func feature(
58 | interface featureName: String,
59 | dependencies: [ProjectDescription.TargetDependency] = [],
60 | resources: ProjectDescription.ResourceFileElements? = []
61 | ) -> Target {
62 | .makeFramework(
63 | name: featureName + "Interface",
64 | sources: [ "interface/**" ],
65 | dependencies: dependencies,
66 | resources: resources
67 | )
68 | }
69 |
70 | static func feature(
71 | implementation featureName: Feature,
72 | dependencies: [ProjectDescription.TargetDependency] = [],
73 | resources: ProjectDescription.ResourceFileElements? = []
74 | ) -> Target {
75 | .feature(
76 | implementation: featureName.rawValue,
77 | dependencies: dependencies,
78 | resources: resources
79 | )
80 | }
81 |
82 | static func feature(
83 | interface featureName: Feature,
84 | dependencies: [ProjectDescription.TargetDependency] = [],
85 | resources: ProjectDescription.ResourceFileElements? = []
86 | ) -> Target {
87 | .feature(
88 | interface: featureName.rawValue,
89 | dependencies: dependencies,
90 | resources: resources
91 | )
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Tuist/ProjectDescriptionHelpers/Project+uFeature.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | public enum Feature: String {
4 | case Common = "Common"
5 | case RandomProvider = "RandomProvider"
6 | case RandomScreen = "RandomScreen"
7 | case App = "App"
8 | }
9 |
10 | public enum External: String {
11 | case FoggyColors = "FoggyColors"
12 | case AsyncAlgorithms = "AsyncAlgorithms"
13 | }
14 |
15 | public extension ProjectDescription.TargetDependency {
16 | private static func feature(target: String, featureName: String) -> ProjectDescription.TargetDependency {
17 | .project(target: target, path: .relativeToRoot("modules/" + featureName))
18 | }
19 |
20 | private static func feature(interface moduleName: String) -> ProjectDescription.TargetDependency {
21 | .feature(target: moduleName + "Interface", featureName: moduleName)
22 | }
23 |
24 | private static func feature(implementation moduleName: String) -> ProjectDescription.TargetDependency {
25 | .feature(target: moduleName, featureName: moduleName)
26 | }
27 |
28 | static func feature(interface moduleName: Feature) -> ProjectDescription.TargetDependency {
29 | .feature(interface: moduleName.rawValue)
30 | }
31 |
32 | static func feature(implementation moduleName: Feature) -> ProjectDescription.TargetDependency {
33 | .feature(implementation: moduleName.rawValue)
34 | }
35 |
36 | static func external(_ module: External) -> ProjectDescription.TargetDependency {
37 | .external(name: module.rawValue)
38 | }
39 |
40 | static var common: ProjectDescription.TargetDependency {
41 | .feature(implementation: .Common)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Tuist/Templates/framework/framework.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | let nameAttribute: Template.Attribute = .required("name")
4 |
5 |
6 | let template = Template(
7 | description: "Framework template",
8 | attributes: [
9 | nameAttribute
10 | ], items: [
11 | .file(
12 | path: "modules/\(nameAttribute)/Project.swift",
13 | templatePath: "project.stencil"
14 | ),
15 | .string(path: "modules/\(nameAttribute)/src/implementation.swift", contents: "// Module \(nameAttribute)\nimport Foundation"),
16 | .string(path: "modules/\(nameAttribute)/interface/interface.swift", contents: "// Module \(nameAttribute)\nimport Foundation")
17 | ]
18 | )
19 |
--------------------------------------------------------------------------------
/Tuist/Templates/framework/project.stencil:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 | import ProjectDescriptionHelpers
3 |
4 | let project = Project(
5 | name: Feature.{{name}}.rawValue,
6 | targets: [
7 | .feature(
8 | interface: .{{name}},
9 | dependencies: [
10 | .common
11 | ]
12 | ),
13 | .feature(
14 | implementation: .{{name}},
15 | dependencies: [
16 | .common,
17 | .feature(interface: .{{name}}),
18 | ],
19 | resources: [
20 | "resources/**"
21 | ]
22 | )
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/Workspace.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | let workspace = Workspace(
4 | name: "ExampleWorkspace",
5 | projects: [
6 | "modules/*"
7 | ]
8 | )
9 |
--------------------------------------------------------------------------------
/modules/App/Derived/InfoPlists/ExampleApp-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | NSHealthRecordsUsageDescription
24 | Your health and workout records will be used localy for visualizing and analyzing your data
25 | NSHealthShareUsageDescription
26 | Your health and workout records will be used localy for visualizing and analyzing your data
27 | NSHealthUpdateUsageDescription
28 | Your health and workout records will be used localy for visualizing and analyzing your data
29 | UILaunchStoryboardName
30 |
31 | UIMainStoryboardFile
32 |
33 | UIRequiredDeviceCapabilities
34 |
35 | armv7
36 |
37 | UISupportedInterfaceOrientations
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationLandscapeLeft
41 | UIInterfaceOrientationLandscapeRight
42 |
43 | UISupportedInterfaceOrientations~ipad
44 |
45 | UIInterfaceOrientationPortrait
46 | UIInterfaceOrientationPortraitUpsideDown
47 | UIInterfaceOrientationLandscapeLeft
48 | UIInterfaceOrientationLandscapeRight
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/modules/App/Project.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 | import ProjectDescriptionHelpers
3 |
4 | let project = Project(
5 | name: Feature.App.rawValue,
6 | targets: [
7 | .makeApp(
8 | name: "ExampleApp",
9 | sources: [
10 | "src/**"
11 | ],
12 | dependencies: [
13 | .common,
14 | .feature(implementation: .RandomProvider),
15 | .feature(interface: .RandomProvider),
16 |
17 | .feature(implementation: .RandomScreen),
18 | .feature(interface: .RandomScreen)
19 | ]
20 | )
21 | ]
22 | )
23 |
--------------------------------------------------------------------------------
/modules/App/src/App.swift:
--------------------------------------------------------------------------------
1 | // Module App
2 | import Foundation
3 | import SwiftUI
4 |
5 | @main
6 | struct ExampleApp: App {
7 | var body: some Scene {
8 | WindowGroup {
9 | MainView()
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/modules/App/src/MainView.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 |
4 | import RandomProvider
5 | import RandomProviderInterface
6 | import RandomScreen
7 | import RandomScreenInterface
8 |
9 | import Common
10 |
11 | struct MainView: View {
12 | enum RandomMode: String, CaseIterable, Identifiable {
13 | case random, zero
14 | var id: Self { self }
15 | }
16 |
17 | enum ScreenMode: String, CaseIterable, Identifiable {
18 | case big, simple
19 | var id: Self { self }
20 | }
21 |
22 | @State private var screen: ScreenMode = .simple
23 | @State private var random: RandomMode = .random
24 |
25 | private var randomRange: ClosedRange {
26 | random == .random ? 0...100 : 0...0
27 | }
28 |
29 | var body: some View {
30 | VStack {
31 | VStack {
32 | Picker("Screen", selection: $screen) {
33 | ForEach(ScreenMode.allCases) { flavor in
34 | Text(flavor.rawValue.capitalized)
35 | }
36 | }
37 | Picker("Random", selection: $random) {
38 | ForEach(RandomMode.allCases) { topping in
39 | Text(topping.rawValue.capitalized)
40 | }
41 | }
42 | }
43 | .pickerStyle(.segmented)
44 | Spacer()
45 | relevantScreen
46 | Spacer()
47 | }
48 | .padding()
49 | }
50 |
51 | @ViewBuilder
52 | private var relevantScreen: some View {
53 | switch screen {
54 | case .simple:
55 | simpleScreen
56 | case .big:
57 | bigScreen
58 | }
59 | }
60 |
61 | private var relevantRandomProvider: NumberProvider {
62 | switch random {
63 | case .random:
64 | return NumberProviderRandom(range: randomRange)
65 | case .zero:
66 | return NumberProviderZero()
67 | }
68 | }
69 |
70 | @ViewBuilder
71 | private var simpleScreen: some View {
72 | HStack {
73 | RandomScreenSimple(randomProvider: relevantRandomProvider)
74 | }
75 | }
76 |
77 | @ViewBuilder
78 | private var bigScreen: some View {
79 | HStack {
80 | Text("\(randomRange.lowerBound)").gray
81 | RandomScreenBig(randomProvider: relevantRandomProvider)
82 | .frame(minWidth: 200)
83 | Text("\(randomRange.upperBound)").gray
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/modules/Common/Derived/InfoPlists/Common-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/modules/Common/Project.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 | import ProjectDescriptionHelpers
3 |
4 | let project = Project(
5 | name: Feature.Common.rawValue,
6 | targets: [
7 | .feature(
8 | implementation: .Common
9 | )
10 | ]
11 | )
12 |
--------------------------------------------------------------------------------
/modules/Common/src/Common.swift:
--------------------------------------------------------------------------------
1 | // Coomon.swift
2 | import SwiftUI
3 |
4 | public extension Text {
5 | var gray: some View {
6 | self.foregroundColor(.gray)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/modules/RandomProvider/Derived/InfoPlists/RandomProvider-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/modules/RandomProvider/Derived/InfoPlists/RandomProviderInterface-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/modules/RandomProvider/Project.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 | import ProjectDescriptionHelpers
3 |
4 | let project = Project(
5 | name: Feature.RandomProvider.rawValue,
6 | targets: [
7 | .feature(
8 | interface: .RandomProvider,
9 | dependencies: [
10 | .common
11 | ]
12 | ),
13 | .feature(
14 | implementation: .RandomProvider,
15 | dependencies: [
16 | .common,
17 | .feature(interface: .RandomProvider),
18 | ]
19 | )
20 | ]
21 | )
22 |
--------------------------------------------------------------------------------
/modules/RandomProvider/interface/NumberProvider.swift:
--------------------------------------------------------------------------------
1 | // Module Biz
2 | import Foundation
3 |
4 | public protocol NumberProvider {
5 | var number: Int { get }
6 | }
7 |
--------------------------------------------------------------------------------
/modules/RandomProvider/src/NumberProviderRandom.swift:
--------------------------------------------------------------------------------
1 | // Module Biz
2 | import Foundation
3 | import RandomProviderInterface
4 |
5 | public struct NumberProviderRandom: NumberProvider {
6 | private let range: ClosedRange
7 |
8 | public var number: Int {
9 | Int.random(in: range)
10 | }
11 |
12 | public init(range: ClosedRange) {
13 | self.range = range
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/modules/RandomProvider/src/NumberProviderZero.swift:
--------------------------------------------------------------------------------
1 | // Module Biz
2 | import Foundation
3 | import RandomProviderInterface
4 |
5 | public struct NumberProviderZero: NumberProvider {
6 | public let number = 0
7 |
8 | public init() {
9 |
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/modules/RandomScreen/Derived/InfoPlists/RandomScreen-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/modules/RandomScreen/Derived/InfoPlists/RandomScreenInterface-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/modules/RandomScreen/Project.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 | import ProjectDescriptionHelpers
3 |
4 | let project = Project(
5 | name: Feature.RandomScreen.rawValue,
6 | targets: [
7 | .feature(
8 | interface: .RandomScreen,
9 | dependencies: [
10 | .common
11 | ]
12 | ),
13 | .feature(
14 | implementation: .RandomScreen,
15 | dependencies: [
16 | .common,
17 | .feature(interface: .RandomScreen),
18 | .feature(interface: .RandomProvider),
19 | ]
20 | )
21 | ]
22 | )
23 |
--------------------------------------------------------------------------------
/modules/RandomScreen/interface/RandomScreen.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 |
4 | public protocol RandomScreen: View {
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/modules/RandomScreen/src/RandomScreenBig.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 | import RandomScreenInterface
4 | import RandomProviderInterface
5 |
6 |
7 | public struct RandomScreenBig: RandomScreen {
8 | let randomProvider: NumberProvider
9 |
10 | @State var number: Int = 0
11 |
12 | public init(randomProvider: NumberProvider) {
13 | self.randomProvider = randomProvider
14 | }
15 |
16 | public var body: some View {
17 | VStack {
18 | Text("\(number)")
19 | .font(.system(size: 80))
20 | Button("generate") {
21 | number = randomProvider.number
22 | }
23 | }.onAppear {
24 | number = randomProvider.number
25 | }
26 | .animation(.default, value: number)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/modules/RandomScreen/src/RandomScreenSimple.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 | import RandomScreenInterface
4 | import RandomProviderInterface
5 |
6 |
7 | public struct RandomScreenSimple: RandomScreen {
8 | let randomProvider: NumberProvider
9 |
10 | @State var number: Int = 0
11 |
12 | public init(randomProvider: NumberProvider) {
13 | self.randomProvider = randomProvider
14 | }
15 |
16 | public var body: some View {
17 | VStack {
18 | Text("\(number)")
19 | Button("generate") {
20 | number = randomProvider.number
21 | }
22 | }.onAppear {
23 | number = randomProvider.number
24 | }
25 | .animation(.default, value: number)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------