├── LICENSE ├── README.md ├── TheComposableArchitecture.xctemplate ├── Default │ ├── ___FILEBASENAME___Action.swift │ ├── ___FILEBASENAME___Environment.swift │ ├── ___FILEBASENAME___Feature.swift │ ├── ___FILEBASENAME___State+view.swift │ ├── ___FILEBASENAME___State.swift │ ├── ___FILEBASENAME___View.Action+feature.swift │ └── ___FILEBASENAME___View.swift ├── FileHeaderComments │ ├── ___FILEBASENAME___Action.swift │ ├── ___FILEBASENAME___Environment.swift │ ├── ___FILEBASENAME___Feature.swift │ ├── ___FILEBASENAME___State+view.swift │ ├── ___FILEBASENAME___State.swift │ ├── ___FILEBASENAME___View.Action+feature.swift │ └── ___FILEBASENAME___View.swift ├── ReducerProtocol │ ├── ___FILEBASENAME___Feature.swift │ ├── ___FILEBASENAME___State+view.swift │ ├── ___FILEBASENAME___View.Action+feature.swift │ └── ___FILEBASENAME___View.swift ├── ReducerProtocolFileHeaderComments │ ├── ___FILEBASENAME___Feature.swift │ ├── ___FILEBASENAME___State+view.swift │ ├── ___FILEBASENAME___View.Action+feature.swift │ └── ___FILEBASENAME___View.swift ├── TemplateIcon.icns └── TemplateInfo.plist ├── install.swift └── xcode-dialog.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Christian Elies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TheComposableArchitecture SwiftUI Template for Xcode 2 | 3 | Swift 5 compatible 4 | Supports iOS, macOS and tvOS 5 | MIT license 6 | 7 | This repository contains a SwiftUI Xcode template for [The Composable Architecture (short: TCA)](https://github.com/pointfreeco/swift-composable-architecture). 8 | 9 | Quickly create all components of a TCA stack using the template in Xcode and focus on implementing your feature 🚀 10 | 11 | ![Xcode dialog](https://github.com/crelies/TheComposableArchitecture-Xcode-Template/blob/main/xcode-dialog.png) 12 | 13 | ## How to use 🛠 14 | 15 | Use the install script. 16 | 17 | ```swift install.swift``` 18 | 19 | ℹ️ **If the script fails for whatever reason just copy the `TheComposableArchitecture.xctemplate` directory into the directory `/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS/Application`.** 20 | 21 | ⚠️ This template is a **project template** (*not a file template*). That's why Xcode does not show the template in the *new file wizard* when you are working on a *Swift package* (*when you opened a Package.swift with Xcode*). 22 | 23 | ## 🤖 Author 24 | 25 | Christian Elies, https://www.christianelies.de 26 | 27 | ## 📄 License 28 | 29 | This Template is available under the MIT license. See the LICENSE file for more info. -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___Action.swift: -------------------------------------------------------------------------------- 1 | enum ___VARIABLE_MODULENAME___Action: Equatable { 2 | case onAppear 3 | } 4 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___Environment.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | 3 | struct ___VARIABLE_MODULENAME___Environment { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___Feature.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | 3 | enum ___VARIABLE_MODULENAME___Feature {} 4 | 5 | extension ___VARIABLE_MODULENAME___Feature { 6 | static var reducer: Reducer<___VARIABLE_MODULENAME___State, ___VARIABLE_MODULENAME___Action, ___VARIABLE_MODULENAME___Environment> { 7 | .init { state, action, environment in 8 | return .none 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___State+view.swift: -------------------------------------------------------------------------------- 1 | extension ___VARIABLE_MODULENAME___State { 2 | var view: ___VARIABLE_MODULENAME___View.State { 3 | .init() 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___State.swift: -------------------------------------------------------------------------------- 1 | struct ___VARIABLE_MODULENAME___State: Equatable { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___View.Action+feature.swift: -------------------------------------------------------------------------------- 1 | extension ___VARIABLE_MODULENAME___View.Action { 2 | var feature: ___VARIABLE_MODULENAME___Action { 3 | switch self { 4 | case .onAppear: 5 | return .onAppear 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/Default/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | struct ___VARIABLE_MODULENAME___View: View { 5 | struct State: Equatable { 6 | 7 | } 8 | 9 | enum Action: Equatable { 10 | case onAppear 11 | } 12 | 13 | let store: Store<___VARIABLE_MODULENAME___State, ___VARIABLE_MODULENAME___Action> 14 | 15 | var body: some View { 16 | WithViewStore( 17 | store.scope( 18 | state: { $0.view }, 19 | action: { (viewAction: ___VARIABLE_MODULENAME___View.Action) in 20 | viewAction.feature 21 | } 22 | ) 23 | ) { viewStore in 24 | Text("Hello world!") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___Action.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | enum ___VARIABLE_MODULENAME___Action: Equatable { 13 | case onAppear 14 | } 15 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___Environment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | import ComposableArchitecture 13 | 14 | struct ___VARIABLE_MODULENAME___Environment { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___Feature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | import ComposableArchitecture 13 | 14 | enum ___VARIABLE_MODULENAME___Feature {} 15 | 16 | extension ___VARIABLE_MODULENAME___Feature { 17 | static var reducer: Reducer<___VARIABLE_MODULENAME___State, ___VARIABLE_MODULENAME___Action, ___VARIABLE_MODULENAME___Environment> { 18 | .init { state, action, environment in 19 | return .none 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___State+view.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | extension ___VARIABLE_MODULENAME___State { 13 | var view: ___VARIABLE_MODULENAME___View.State { 14 | .init() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___State.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | struct ___VARIABLE_MODULENAME___State: Equatable { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___View.Action+feature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | extension ___VARIABLE_MODULENAME___View.Action { 13 | var feature: ___VARIABLE_MODULENAME___Action { 14 | switch self { 15 | case .onAppear: 16 | return .onAppear 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/FileHeaderComments/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | import ComposableArchitecture 13 | import SwiftUI 14 | 15 | struct ___VARIABLE_MODULENAME___View: View { 16 | struct State: Equatable { 17 | 18 | } 19 | 20 | enum Action: Equatable { 21 | case onAppear 22 | } 23 | 24 | let store: Store<___VARIABLE_MODULENAME___State, ___VARIABLE_MODULENAME___Action> 25 | 26 | var body: some View { 27 | WithViewStore( 28 | store.scope( 29 | state: { $0.view }, 30 | action: { (viewAction: ___VARIABLE_MODULENAME___View.Action) in 31 | viewAction.feature 32 | } 33 | ) 34 | ) { viewStore in 35 | Text("Hello world!") 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/ReducerProtocol/___FILEBASENAME___Feature.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | 3 | struct ___VARIABLE_MODULENAME___Feature: ReducerProtocol { 4 | struct State: Equatable {} 5 | 6 | enum Action: Equatable { 7 | case onAppear 8 | } 9 | 10 | var body: some ReducerProtocol { 11 | Reduce { state, action in 12 | return .none 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/ReducerProtocol/___FILEBASENAME___State+view.swift: -------------------------------------------------------------------------------- 1 | extension ___VARIABLE_MODULENAME___Feature.State { 2 | var view: ___VARIABLE_MODULENAME___View.State { 3 | .init(state: self) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/ReducerProtocol/___FILEBASENAME___View.Action+feature.swift: -------------------------------------------------------------------------------- 1 | extension ___VARIABLE_MODULENAME___View.Action { 2 | var feature: ___VARIABLE_MODULENAME___Feature.Action { 3 | switch self { 4 | case .onAppear: 5 | return .onAppear 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/ReducerProtocol/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | 4 | struct ___VARIABLE_MODULENAME___View: View { 5 | struct State: Equatable { 6 | init(state: ___VARIABLE_MODULENAME___Feature.State) {} 7 | } 8 | 9 | enum Action: Equatable { 10 | case onAppear 11 | } 12 | 13 | let store: StoreOf<___VARIABLE_MODULENAME___Feature> 14 | 15 | var body: some View { 16 | WithViewStore( 17 | self.store, 18 | observe: ___VARIABLE_MODULENAME___View.State.init, 19 | send: { (viewAction: Action) in viewAction.feature } 20 | ) { viewStore in 21 | Text("Hello world!") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/ReducerProtocolFileHeaderComments/___FILEBASENAME___Feature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | import ComposableArchitecture 13 | 14 | struct ___VARIABLE_MODULENAME___Feature: ReducerProtocol { 15 | struct State: Equatable {} 16 | 17 | enum Action: Equatable { 18 | case onAppear 19 | } 20 | 21 | var body: some ReducerProtocol { 22 | Reduce { state, action in 23 | return .none 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/ReducerProtocolFileHeaderComments/___FILEBASENAME___State+view.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | extension ___VARIABLE_MODULENAME___Feature.State { 13 | var view: ___VARIABLE_MODULENAME___View.State { 14 | .init(state: self) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/ReducerProtocolFileHeaderComments/___FILEBASENAME___View.Action+feature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | extension ___VARIABLE_MODULENAME___View.Action { 13 | var feature: ___VARIABLE_MODULENAME___Feature.Action { 14 | switch self { 15 | case .onAppear: 16 | return .onAppear 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/ReducerProtocolFileHeaderComments/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | // Template generated by Christian Elies @crelies 9 | // https://www.christianelies.de 10 | // 11 | 12 | import ComposableArchitecture 13 | import SwiftUI 14 | 15 | struct ___VARIABLE_MODULENAME___View: View { 16 | struct State: Equatable { 17 | init(state: ___VARIABLE_MODULENAME___Feature.State) {} 18 | } 19 | 20 | enum Action: Equatable { 21 | case onAppear 22 | } 23 | 24 | let store: StoreOf<___VARIABLE_MODULENAME___Feature> 25 | 26 | var body: some View { 27 | WithViewStore( 28 | self.store, 29 | observe: ___VARIABLE_MODULENAME___View.State.init, 30 | send: { (viewAction: Action) in viewAction.feature } 31 | ) { viewStore in 32 | Text("Hello world!") 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/TemplateIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-swift-dev/TheComposableArchitecture-Xcode-Template/7fe79d651a54e2dc420f73dea54055ea184c2672/TheComposableArchitecture.xctemplate/TemplateIcon.icns -------------------------------------------------------------------------------- /TheComposableArchitecture.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AllowedTypes 6 | 7 | public.swift-source 8 | 9 | Platforms 10 | 11 | com.apple.platform.iphoneos 12 | 13 | DefaultCompletionName 14 | SwiftTCAComponents 15 | Description 16 | TheComposableArchitecture Components 17 | Kind 18 | Xcode.IDEKit.TextSubstitutionFileTemplateKind 19 | MainTemplateFile 20 | ___FILEBASENAME___.swift 21 | SortOrder 22 | 1 23 | Template Author 24 | Christian Elies @ 2023 25 | Options 26 | 27 | 28 | Description 29 | Name of feature you want to implement 30 | Default 31 | 32 | Identifier 33 | MODULENAME 34 | Name 35 | Feature name: 36 | Required 37 | YES 38 | Type 39 | text 40 | 41 | 42 | Default 43 | ___VARIABLE_MODULENAME___ 44 | Identifier 45 | productName 46 | Type 47 | static 48 | 49 | 50 | Description 51 | Use ReducerProtocol 52 | Default 53 | false 54 | Identifier 55 | ReducerProtocol 56 | Name 57 | Use ReducerProtocol 58 | Required 59 | YES 60 | Type 61 | checkbox 62 | 63 | 64 | Description 65 | Add file header comments 66 | Default 67 | false 68 | Identifier 69 | FileHeaderComments 70 | Name 71 | Add file header comments 72 | Required 73 | YES 74 | Type 75 | checkbox 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /install.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Constants { 4 | enum CommandLineValues { 5 | static let yes = "y" 6 | static let no = "n" 7 | } 8 | 9 | enum File { 10 | static let templateName = "TheComposableArchitecture.xctemplate" 11 | static let destinationRelativePath = "/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS/Application" 12 | } 13 | 14 | enum Messages { 15 | static let successMessage = "✅ Template was installed succesfully 🎉. Enjoy it 🙂" 16 | static let successfullReplaceMessage = "✅ The Template has been replaced for you with the new version 🎉. Enjoy it 🙂" 17 | static let errorMessage = "❌ Ooops! Something went wrong 😡" 18 | static let exitMessage = "Bye Bye 👋" 19 | static let promptReplace = "That Template already exists. Do you want to replace it? (y or n)" 20 | } 21 | 22 | enum Blocks { 23 | static let printSeparator = { print("====================================") } 24 | } 25 | } 26 | 27 | func printToConsole(_ message:Any) { 28 | Constants.Blocks.printSeparator() 29 | print("\(message)") 30 | Constants.Blocks.printSeparator() 31 | } 32 | 33 | func moveTemplate() { 34 | do { 35 | let fileManager = FileManager.default 36 | let destinationPath = bash(command: "xcode-select", arguments: ["--print-path"]).appending(Constants.File.destinationRelativePath) 37 | 38 | printToConsole("Template will be copied to: \(destinationPath)") 39 | 40 | if !fileManager.fileExists(atPath: "\(destinationPath)/\(Constants.File.templateName)") { 41 | try fileManager.copyItem(atPath: Constants.File.templateName, toPath: "\(destinationPath)/\(Constants.File.templateName)") 42 | printToConsole(Constants.Messages.successMessage) 43 | } else { 44 | print(Constants.Messages.promptReplace) 45 | 46 | var input = "" 47 | repeat { 48 | guard let textFormCommandLine = readLine(strippingNewline: true) else { 49 | continue 50 | } 51 | input = textFormCommandLine 52 | } while(input != Constants.CommandLineValues.yes && input != Constants.CommandLineValues.no) 53 | 54 | if input == Constants.CommandLineValues.yes { 55 | try replaceItemAt(URL(fileURLWithPath: "\(destinationPath)/\(Constants.File.templateName)"), withItemAt: URL(fileURLWithPath: Constants.File.templateName)) 56 | printToConsole(Constants.Messages.successfullReplaceMessage) 57 | } else { 58 | print(Constants.Messages.exitMessage) 59 | } 60 | } 61 | } catch let error as NSError { 62 | printToConsole("\(Constants.Messages.errorMessage) : \(error.localizedFailureReason!)") 63 | } 64 | } 65 | 66 | func replaceItemAt(_ url: URL, withItemAt itemAtUrl: URL) throws { 67 | let fileManager = FileManager.default 68 | try fileManager.removeItem(at: url) 69 | try fileManager.copyItem(atPath: itemAtUrl.path, toPath: url.path) 70 | } 71 | 72 | func shell(launchPath: String, arguments: [String]) -> String { 73 | let task = Process() 74 | task.launchPath = launchPath 75 | task.arguments = arguments 76 | 77 | let pipe = Pipe() 78 | task.standardOutput = pipe 79 | task.launch() 80 | 81 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 82 | let output = String(data: data, encoding: String.Encoding.utf8)! 83 | if output.count > 0 { 84 | // remove newline character. 85 | let lastIndex = output.index(before: output.endIndex) 86 | return String(output[output.startIndex ..< lastIndex]) 87 | } 88 | return output 89 | } 90 | 91 | func bash(command: String, arguments: [String]) -> String { 92 | let whichPathForCommand = shell(launchPath: "/bin/bash", arguments: [ "-l", "-c", "which \(command)" ]) 93 | return shell(launchPath: whichPathForCommand, arguments: arguments) 94 | } 95 | 96 | moveTemplate() 97 | -------------------------------------------------------------------------------- /xcode-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-swift-dev/TheComposableArchitecture-Xcode-Template/7fe79d651a54e2dc420f73dea54055ea184c2672/xcode-dialog.png --------------------------------------------------------------------------------