├── SwiftUIDemos
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Example Views
│ ├── Tagger
│ │ ├── SubViews
│ │ │ ├── ErrorMessageView.swift
│ │ │ ├── TagView.swift
│ │ │ ├── TagListView.swift
│ │ │ └── TagEntryView.swift
│ │ └── TaggerView.swift
│ ├── Toast
│ │ ├── ToastExampleView.swift
│ │ └── SubViews
│ │ │ └── ToastOverlayView.swift
│ └── FilterGroupedList
│ │ ├── DummyListData.swift
│ │ ├── SubViews
│ │ ├── SearchBarView.swift
│ │ ├── PeopleListView.swift
│ │ └── DepartmentListView.swift
│ │ └── GroupedListView.swift
├── SwiftUIDemosApp.swift
└── Info.plist
├── SwiftUIDemos.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── alexhay.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
└── README.md
/SwiftUIDemos/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftUIDemos.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SwiftUIDemos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftUIDemos.xcodeproj/xcuserdata/alexhay.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | SwiftUIDemos.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/Tagger/SubViews/ErrorMessageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ErrorMessageView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /// A subview that displays a message when an error occurs
11 | struct ErrorMessageView: View {
12 |
13 | @Binding var showingError: Bool
14 | @Binding var errorString: String
15 |
16 | var body: some View {
17 | HStack {
18 | Image(systemName: "exclamationmark.triangle.fill")
19 | .foregroundColor(.orange)
20 | Text(errorString)
21 | .foregroundColor(.secondary)
22 | .padding(.leading, -6)
23 | }
24 | .font(.caption)
25 | .opacity(showingError ? 1 : 0)
26 | .animation(.easeIn(duration: 0.3))
27 | }
28 | }
29 |
30 | struct ErrorMessageView_Previews: PreviewProvider {
31 | static var previews: some View {
32 | ErrorMessageView(showingError: .constant(true), errorString: .constant("Tag can't be a duplicate"))
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/Tagger/SubViews/TagView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TagView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /// A subview of a tag shown in a list. When tapped the tag will be removed from the array
11 | struct TagView: View {
12 |
13 | var tag: String
14 | @Binding var tags: [String]
15 |
16 | var body: some View {
17 | HStack {
18 | Text(tag.lowercased())
19 | .padding(.leading, 2)
20 | Image(systemName: "xmark.circle.fill")
21 | .opacity(0.4)
22 | .padding(.leading, -6)
23 | }
24 | .foregroundColor(.white)
25 | .font(.caption2)
26 | .padding(4)
27 | .background(Color.blue.cornerRadius(5))
28 | .padding(4)
29 | .onTapGesture {
30 | tags = tags.filter({ $0 != tag })
31 | }
32 | }
33 | }
34 |
35 | struct TagView_Previews: PreviewProvider {
36 | static var previews: some View {
37 | TagView(tag: "hello world", tags: .constant(["tag one", "tag two"]))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SwiftUIDemos/SwiftUIDemosApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftUIDemosApp.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct SwiftUIDemosApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | IndexView()
15 | }
16 | }
17 | }
18 |
19 | struct IndexView: View {
20 |
21 | var body: some View {
22 | NavigationView {
23 | List {
24 | NavigationLink(
25 | destination: TaggerView(),
26 | label: {
27 | Image(systemName: "tag")
28 | Text("Tagger")
29 | }
30 | )
31 | NavigationLink(
32 | destination: GroupedListView(),
33 | label: {
34 | Image(systemName: "rectangle.grid.1x2")
35 | Text("Grouped List")
36 | }
37 | )
38 | NavigationLink(
39 | destination: ToastExampleView(),
40 | label: {
41 | Image(systemName: "rectangle.bottomthird.inset.fill")
42 | Text("Toast")
43 | }
44 | )
45 | }
46 | .listStyle(InsetGroupedListStyle())
47 | .navigationBarTitle(Text("Example Views"))
48 | }
49 | }
50 | }
51 |
52 | struct IndexView_Previews: PreviewProvider {
53 | static var previews: some View {
54 | IndexView()
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/Toast/ToastExampleView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToastExampleView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ToastExampleView: View {
11 |
12 | @State var textToCopy = "hello world"
13 | @State var showingToast = false
14 |
15 | var body: some View {
16 | VStack(alignment: .center) {
17 | TextField("Text To Copy", text: $textToCopy)
18 | .textFieldStyle(RoundedBorderTextFieldStyle())
19 | Button(action: {
20 | UIPasteboard.general.string = textToCopy
21 | showingToast = true
22 | }, label: {
23 | Text("Copy Text")
24 | .foregroundColor(.white)
25 | .padding(.horizontal)
26 | .padding(.vertical, 7)
27 | .background(Color.blue.cornerRadius(8))
28 | .padding()
29 | })
30 | Spacer()
31 | }
32 | .padding()
33 | .toast(isPresented: $showingToast) {
34 | HStack {
35 | Image(systemName: "checkmark.circle")
36 | Text("Copied To Clipboard")
37 | }
38 | }
39 | .navigationBarTitle(Text("Toast"))
40 | }
41 | }
42 |
43 | struct ToastExampleView_Previews: PreviewProvider {
44 | static var previews: some View {
45 | ToastExampleView()
46 | }
47 | }
48 |
49 |
50 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/FilterGroupedList/DummyListData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Countries+PeopleModel.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: DUMMY DATA
11 | class DummyData {
12 | static let shared = DummyData()
13 | var names: [String] = ["Alex","Emma","Simon","Jane","Paul", "John", "Jane", "Mary", "Jack", "Linda"]
14 | var people: [Person] = []
15 | private init() {
16 | people = makePeople(withNames: names)
17 | }
18 | }
19 |
20 | // Generates some randomised dummy data from the array of names when run
21 | extension DummyData {
22 | func makePeople(withNames names: [String]) -> [Person] {
23 | return names.map({ Person(name: $0, department: Departments.allCases.randomElement() ?? .design) })
24 | }
25 | }
26 |
27 | // MARK: DUMMY DATA MODEL
28 | struct Person: Identifiable {
29 | let id: UUID = UUID()
30 | let name: String
31 | let department: Departments
32 | }
33 |
34 | enum Departments: String, CaseIterable {
35 | case development = "Development"
36 | case design = "Design"
37 | case sales = "Sales"
38 | case marketing = "Marketing"
39 | }
40 |
41 | // Allows the departments enum to be sorted by their raw value
42 | extension Departments: Comparable {
43 | static func < (lhs: Departments, rhs: Departments) -> Bool { lhs.rawValue < rhs.rawValue }
44 | }
45 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/Tagger/TaggerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TaggerView.swift
3 | //
4 | // Created by Alex Hay on 21/11/2020.
5 | //
6 | // Simple interface for adding tags to an array in SwiftUI
7 | // Example video: https://imgur.com/gallery/CcA1IXp
8 | // alignmentGuide code from Asperi @ https://stackoverflow.com/a/58876712/11685049
9 |
10 | import SwiftUI
11 |
12 | /// The main view to add tags to an array
13 | struct TaggerView: View {
14 |
15 | @State var newTag = ""
16 | @State var tags = ["example","hello world"]
17 | @State var showingError = false
18 | @State var errorString = "x" // Can't start with empty string or view will pop as size changes
19 |
20 | var body: some View {
21 | VStack(alignment: .leading) {
22 | ErrorMessageView(showingError: $showingError, errorString: $errorString)
23 | TagEntryView(newTag: $newTag, tags: $tags, showingError: $showingError, errorString: $errorString)
24 | TagListView(tags: $tags)
25 | }
26 | .padding()
27 | .onChange(of: showingError, perform: { value in
28 | if value {
29 | // Hide the error message after a delay
30 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
31 | showingError = false
32 | }
33 | }
34 | })
35 | .navigationBarTitle(Text("Tagger"))
36 | }
37 | }
38 |
39 | struct TaggerView_Previews: PreviewProvider {
40 | static var previews: some View {
41 | TaggerView()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/FilterGroupedList/SubViews/SearchBarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | // MARK: SEARCH BAR
11 | /// Search box for filtering the list
12 | struct SearchBarView: View {
13 |
14 | @Binding var searchQuery: String
15 |
16 | var body: some View {
17 |
18 | HStack {
19 | Spacer()
20 | TextField("Search", text: $searchQuery)
21 | .modifier(ClearButtonModifier(text: $searchQuery))
22 | .textFieldStyle(RoundedBorderTextFieldStyle())
23 | .frame(minWidth: nil, idealWidth: nil, maxWidth: 240, minHeight: nil, idealHeight: nil, maxHeight: nil, alignment: .center)
24 | Spacer()
25 | }
26 | }
27 | }
28 |
29 | // MARK: CLEAR BUTTON
30 | /// Adds a button next to the search field that clears the search query when pressed
31 | public struct ClearButtonModifier: ViewModifier {
32 |
33 | @Binding var text: String
34 |
35 | public func body(content: Content) -> some View {
36 | HStack {
37 | content
38 | Image(systemName: "multiply.circle.fill")
39 | .foregroundColor(.secondary)
40 | .opacity(text == "" ? 0 : 0.5)
41 | .onTapGesture {
42 | text = ""
43 | }
44 | .animation(.easeInOut(duration: 0.2))
45 | }
46 | }
47 | }
48 |
49 | struct SearchBarView_Previews: PreviewProvider {
50 | static var previews: some View {
51 | SearchBarView(searchQuery: .constant("hello world"))
52 | SearchBarView(searchQuery: .constant(""))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftUI-Demos
2 |
3 | As I learn SwiftUI I'm adding generic interfaces I build to this repo as examples of different types of UIs that can be created and adapted.
4 |
5 | The project is built for for iOS14+
6 |
7 | So far it includes:
8 |
9 | ## Tagger
10 | A simple interface for adding and removing tags to an array. It includes a useful tag list that dynamically flows elements onto new rows and error handling.
11 |
12 | 
13 |
14 | ## Grouped List
15 | An inset list that has a search field for filtering and a picker to choose between grouped & non-grouped list. Includes disclosure groups for hiding grouped content.
16 |
17 | 
18 |
19 | ## Toast
20 | A notification view that is temporarily displayed on top of the UI.
21 |
22 | 
23 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/Toast/SubViews/ToastOverlayView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToastOverlayView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 24/11/2020.
6 | //
7 | // Adapted from this StackOverflow answer: https://stackoverflow.com/a/56550601/11685049
8 |
9 | import SwiftUI
10 |
11 | /// A convenience method on View for presenting a toast
12 | extension View {
13 | func toast(isPresented: Binding, content: @escaping () -> Content) -> some View where Content: View {
14 | Toast(isPresented: isPresented, presenter: { self }, content: content)
15 | }
16 | }
17 |
18 | struct Toast: View where Presenting: View, Content: View {
19 |
20 | @Binding var isPresented: Bool
21 | let presenter: () -> Presenting // The view that will be "presenting" this toast
22 | let content: () -> Content // The toast overlay view
23 | let delay: TimeInterval = 1.5 // How long the toast will display before disappearing
24 |
25 | var body: some View {
26 | if isPresented {
27 | DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
28 | withAnimation {
29 | isPresented = false
30 | }
31 | }
32 | }
33 |
34 | return GeometryReader { geometry in
35 | ZStack(alignment: .bottom) {
36 | presenter()
37 | ZStack {
38 | RoundedRectangle(cornerRadius: 15)
39 | .foregroundColor(Color(UIColor.tertiarySystemBackground))
40 | .shadow(radius: 7)
41 | content()
42 | }
43 | .frame(width: geometry.size.width / 1.5, height: geometry.size.height / 13)
44 | .opacity(isPresented ? 1 : 0)
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/Tagger/SubViews/TagListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TagListView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /// A subview containing a list of all tags that are in the array. Tags flow onto the next line when wider than the view's width
11 | struct TagListView: View {
12 |
13 | @Binding var tags: [String]
14 |
15 | var body: some View {
16 | GeometryReader { geo in
17 | generateTags(in: geo)
18 | .padding(.top)
19 | }
20 | }
21 |
22 | /// Adds a tag view for each tag in the array. Populates from left to right and then on to new rows when too wide for the screen
23 | private func generateTags(in geo: GeometryProxy) -> some View {
24 | var width: CGFloat = 0
25 | var height: CGFloat = 0
26 |
27 | return ZStack(alignment: .topLeading) {
28 | ForEach(tags, id: \.self) { tag in
29 | TagView(tag: tag, tags: $tags)
30 | .alignmentGuide(.leading, computeValue: { tagSize in
31 | if (abs(width - tagSize.width) > geo.size.width) {
32 | width = 0
33 | height -= tagSize.height
34 | }
35 | let offset = width
36 | if tag == tags.last ?? "" {
37 | width = 0
38 | } else {
39 | width -= tagSize.width
40 | }
41 | return offset
42 | })
43 | .alignmentGuide(.top, computeValue: { tagSize in
44 | let offset = height
45 | if tag == tags.last ?? "" {
46 | height = 0
47 | }
48 | return offset
49 | })
50 | }
51 | }
52 | }
53 | }
54 | struct TagListView_Previews: PreviewProvider {
55 | static var previews: some View {
56 | TagListView(tags: .constant(["tag one", "tag two"]))
57 | .padding()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/FilterGroupedList/SubViews/PeopleListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PeopleListView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | // MARK: PEOPLE LIST
11 | struct PeopleListView: View {
12 |
13 | var people: [Person]
14 | var showDepartmentName: Bool
15 |
16 | var body: some View {
17 | ForEach(people, id: \.id) { person in
18 | Label(
19 | title: {
20 | VStack(alignment: .leading) {
21 | Text(person.name)
22 | if showDepartmentName {
23 | Text(person.department.rawValue)
24 | .foregroundColor(.secondary)
25 | .font(.caption)
26 | }
27 | }
28 | },
29 | icon: {
30 | Image(systemName: getIconName(forDepartment: person.department))
31 | .foregroundColor(.primary)
32 | }
33 | )
34 | .padding(.vertical, 6)
35 | }
36 | }
37 |
38 | // Retrieves a different SFSymbol name for each department
39 | func getIconName(forDepartment department: Departments) -> String {
40 | switch department {
41 | case .design:
42 | return "paintbrush.pointed"
43 | case .development:
44 | return "curlybraces.square"
45 | case .marketing:
46 | return "bubble.left"
47 | case .sales:
48 | return "dollarsign.circle"
49 | }
50 | }
51 | }
52 |
53 | struct PeopleListView_Previews: PreviewProvider {
54 | static var previews: some View {
55 | List {
56 | PeopleListView(people: DummyData.shared.people, showDepartmentName: true)
57 | }
58 | .listStyle(InsetGroupedListStyle())
59 | List {
60 | PeopleListView(people: DummyData.shared.people, showDepartmentName: false)
61 | }
62 | .listStyle(InsetGroupedListStyle())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/FilterGroupedList/SubViews/DepartmentListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DepartmentListView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | // MARK: DEPARTMENT LIST
11 | struct DepartmentListView: View {
12 |
13 | @Binding var searchQuery: String
14 |
15 | var body: some View {
16 | ForEach(Departments.allCases.sorted(), id: \.self) { department in
17 | if let people = getPeople(fromDepartment: department), !people.isEmpty {
18 | DisclosureGroup(
19 | content: {
20 | PeopleListView(people: people, showDepartmentName: false)
21 | },
22 | label: {
23 | HStack {
24 | Text(department.rawValue)
25 | Spacer()
26 | Text("\(people.count)")
27 | .foregroundColor(.secondary)
28 | }
29 | }
30 | )
31 | }
32 | }
33 | }
34 |
35 | /// Gets all dummy people from the given department and filters them down to those with names containing the search query
36 | func getPeople(fromDepartment department: Departments) -> [Person] {
37 | let people: [Person] = DummyData.shared.people
38 | var results = [Person]()
39 | if searchQuery == "" {
40 | results = people.filter({ $0.department == department })
41 | } else {
42 | let lowercaseQuery = searchQuery.lowercased()
43 | results = people.filter({ $0.department == department && $0.name.lowercased().contains(lowercaseQuery) })
44 | }
45 | return results.sorted(by: {$0.name < $1.name})
46 | }
47 | }
48 |
49 | struct DepartmentListView_Previews: PreviewProvider {
50 | static var previews: some View {
51 | List {
52 | DepartmentListView(searchQuery: .constant(""))
53 | .padding()
54 | }
55 | .listStyle(InsetGroupedListStyle())
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/SwiftUIDemos/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 |
28 | UIApplicationSupportsIndirectInputEvents
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/FilterGroupedList/GroupedListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GroupedListView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 | // Filterable groupd list with disclosure groups
8 |
9 | import SwiftUI
10 |
11 | // MARK: GROUPED LIST
12 | struct GroupedListView: View {
13 |
14 | @Environment(\.colorScheme) var colorScheme // Check whether the environment is .light or .dark
15 | @State private var pickerChoice: PickerChoice = .allStaff
16 | @State private var searchQuery: String = ""
17 |
18 | var body: some View {
19 | List {
20 | Section {
21 | // Choose between "Staff" or "Departments" list views
22 | Picker("", selection: $pickerChoice) {
23 | ForEach(PickerChoice.allCases, id: \.self) { value in
24 | Text(value.rawValue)
25 | }
26 | }
27 | // Hides the grouped list background
28 | .listRowBackground(colorScheme == .light ? Color(.systemGray6) : Color(.systemBackground))
29 | .pickerStyle(SegmentedPickerStyle())
30 | }
31 | Section {
32 | SearchBarView(searchQuery: $searchQuery)
33 | .listRowBackground(colorScheme == .light ? Color(.systemGray6) : Color(.systemBackground))
34 | }
35 | Section {
36 | switch pickerChoice {
37 | case .allStaff:
38 | PeopleListView(people: filterPeople(), showDepartmentName: true)
39 | case .byDepartment:
40 | DepartmentListView(searchQuery: $searchQuery)
41 | }
42 | }
43 | }
44 | .listStyle(InsetGroupedListStyle())
45 | .navigationTitle(Text("Personnel"))
46 |
47 | }
48 |
49 | enum PickerChoice: String, CaseIterable {
50 | case allStaff = "Staff"
51 | case byDepartment = "Departments"
52 | }
53 |
54 | /// Gets all dummy people and filters them down to those with names or departments containing the search query
55 | func filterPeople() -> [Person] {
56 | let allPeople = DummyData.shared.people
57 | if searchQuery == "" {
58 | return allPeople
59 | } else {
60 | let lowercaseQuery = searchQuery.lowercased()
61 | let filteredPeople = allPeople.filter({
62 | $0.name.lowercased().contains(lowercaseQuery) || $0.department.rawValue.lowercased().contains(lowercaseQuery)
63 | })
64 | return filteredPeople.sorted(by: {$0.name < $1.name})
65 | }
66 | }
67 | }
68 |
69 | // MARK: PREVIEWS
70 | struct GroupedListView_Previews: PreviewProvider {
71 | static var previews: some View {
72 | GroupedListView()
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/SwiftUIDemos/Example Views/Tagger/SubViews/TagEntryView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TagEntryView.swift
3 | // SwiftUIDemos
4 | //
5 | // Created by Alex Hay on 23/11/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /// A subview that contains the text-entry field for entering new tags
11 | struct TagEntryView: View {
12 |
13 | @Binding var newTag: String
14 | @Binding var tags: [String]
15 | @Binding var showingError: Bool
16 | @Binding var errorString: String
17 |
18 | var body: some View {
19 | HStack {
20 | TextField("Add Tags", text: $newTag, onCommit: {
21 | addTag(newTag)
22 | })
23 | .textFieldStyle(RoundedBorderTextFieldStyle())
24 | .autocapitalization(.none)
25 | Spacer()
26 | Image(systemName: "plus.circle")
27 | // Entry button is blue if the tag is a valid entry
28 | .foregroundColor((isTagValid(newTag) == .IsValid) ? .blue : .secondary)
29 | .onTapGesture {
30 | addTag(newTag)
31 | }
32 | }
33 | .onChange(of: newTag, perform: { value in
34 | if value.contains(",") {
35 | newTag = value.replacingOccurrences(of: ",", with: "")
36 | if value != "," { // Don't enter a tag if the user just types ","
37 | // Try to add the tag if user types a comma
38 | addTag(newTag)
39 | }
40 | }
41 | })
42 | }
43 |
44 | /// Checks if the entered text is valid as a tag
45 | private func isTagValid(_ tag: String) -> ErrorCode {
46 | // Invalid tags:
47 | // - empty strings
48 | // - tags already in the tag array
49 | let lowerTag = tag.lowercased()
50 | if lowerTag == "" {
51 | return .Empty
52 | } else if tags.contains(lowerTag) {
53 | return .Duplicate
54 | } else {
55 | return .IsValid
56 | }
57 | }
58 |
59 | /// If the tag is valid, it is added to an array, otherwise the error message is shown
60 | private func addTag(_ tag: String) {
61 | let code = isTagValid(tag)
62 | switch code {
63 | case .Duplicate, .Empty:
64 | showError(code)
65 | case .IsValid:
66 | tags.append(newTag.lowercased())
67 | newTag = ""
68 | }
69 | }
70 |
71 | /// Shows error text above the tag entry field
72 | private func showError(_ code: ErrorCode) {
73 | errorString = code.rawValue
74 | showingError = true
75 | }
76 |
77 | enum ErrorCode: String {
78 | case Empty = "Tag can't be empty"
79 | case Duplicate = "Tag can't be a duplicate"
80 | case IsValid = ""
81 | }
82 | }
83 |
84 | struct TagEntryView_Previews: PreviewProvider {
85 | static var previews: some View {
86 | VStack {
87 | TagEntryView(newTag: .constant(""), tags: .constant(["tag one", "tag two"]), showingError: .constant(false), errorString: .constant("Tag can't be a duplicate"))
88 | .padding()
89 | TagEntryView(newTag: .constant("hello world"), tags: .constant(["tag one", "tag two"]), showingError: .constant(true), errorString: .constant("Tag can't be a duplicate"))
90 | .padding()
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/SwiftUIDemos.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 8C2FB90E256C600600BD5CD9 /* DepartmentListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2FB90D256C600600BD5CD9 /* DepartmentListView.swift */; };
11 | 8C2FB911256C608600BD5CD9 /* PeopleListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2FB910256C608600BD5CD9 /* PeopleListView.swift */; };
12 | 8C2FB914256C616A00BD5CD9 /* SearchBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2FB913256C616A00BD5CD9 /* SearchBarView.swift */; };
13 | 8C2FB91D256C828E00BD5CD9 /* ToastExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2FB91C256C828E00BD5CD9 /* ToastExampleView.swift */; };
14 | 8C2FB922256C850500BD5CD9 /* ToastOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2FB921256C850500BD5CD9 /* ToastOverlayView.swift */; };
15 | 8CA247AF256BAE580006B376 /* SwiftUIDemosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA247AE256BAE580006B376 /* SwiftUIDemosApp.swift */; };
16 | 8CA247B3256BAE5B0006B376 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8CA247B2256BAE5B0006B376 /* Assets.xcassets */; };
17 | 8CA247B6256BAE5B0006B376 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8CA247B5256BAE5B0006B376 /* Preview Assets.xcassets */; };
18 | 8CA247BF256BAE6F0006B376 /* TaggerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA247BE256BAE6F0006B376 /* TaggerView.swift */; };
19 | 8CA247CA256BB6DD0006B376 /* ErrorMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA247C9256BB6DD0006B376 /* ErrorMessageView.swift */; };
20 | 8CA247CF256BB7890006B376 /* TagEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA247CE256BB7890006B376 /* TagEntryView.swift */; };
21 | 8CA247D2256BB8D60006B376 /* TagListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA247D1256BB8D60006B376 /* TagListView.swift */; };
22 | 8CA247D6256BB9300006B376 /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA247D5256BB9300006B376 /* TagView.swift */; };
23 | 8CA247DB256C1D260006B376 /* GroupedListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA247DA256C1D260006B376 /* GroupedListView.swift */; };
24 | 8CA247E0256C1D580006B376 /* DummyListData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA247DF256C1D580006B376 /* DummyListData.swift */; };
25 | /* End PBXBuildFile section */
26 |
27 | /* Begin PBXFileReference section */
28 | 8C2FB90D256C600600BD5CD9 /* DepartmentListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DepartmentListView.swift; sourceTree = ""; };
29 | 8C2FB910256C608600BD5CD9 /* PeopleListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleListView.swift; sourceTree = ""; };
30 | 8C2FB913256C616A00BD5CD9 /* SearchBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarView.swift; sourceTree = ""; };
31 | 8C2FB91C256C828E00BD5CD9 /* ToastExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastExampleView.swift; sourceTree = ""; };
32 | 8C2FB921256C850500BD5CD9 /* ToastOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastOverlayView.swift; sourceTree = ""; };
33 | 8CA247AB256BAE580006B376 /* SwiftUIDemos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIDemos.app; sourceTree = BUILT_PRODUCTS_DIR; };
34 | 8CA247AE256BAE580006B376 /* SwiftUIDemosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIDemosApp.swift; sourceTree = ""; };
35 | 8CA247B2256BAE5B0006B376 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
36 | 8CA247B5256BAE5B0006B376 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
37 | 8CA247B7256BAE5B0006B376 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
38 | 8CA247BE256BAE6F0006B376 /* TaggerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaggerView.swift; sourceTree = ""; };
39 | 8CA247C9256BB6DD0006B376 /* ErrorMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessageView.swift; sourceTree = ""; };
40 | 8CA247CE256BB7890006B376 /* TagEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagEntryView.swift; sourceTree = ""; };
41 | 8CA247D1256BB8D60006B376 /* TagListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagListView.swift; sourceTree = ""; };
42 | 8CA247D5256BB9300006B376 /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = ""; };
43 | 8CA247DA256C1D260006B376 /* GroupedListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupedListView.swift; sourceTree = ""; };
44 | 8CA247DF256C1D580006B376 /* DummyListData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyListData.swift; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 8CA247A8256BAE580006B376 /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 8C2FB909256C2F0D00BD5CD9 /* SubViews */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 8C2FB90D256C600600BD5CD9 /* DepartmentListView.swift */,
62 | 8C2FB910256C608600BD5CD9 /* PeopleListView.swift */,
63 | 8C2FB913256C616A00BD5CD9 /* SearchBarView.swift */,
64 | );
65 | path = SubViews;
66 | sourceTree = "";
67 | };
68 | 8C2FB919256C677000BD5CD9 /* Example Views */ = {
69 | isa = PBXGroup;
70 | children = (
71 | 8C2FB91B256C826500BD5CD9 /* Toast */,
72 | 8CA247D9256C1CB80006B376 /* FilterGroupedList */,
73 | 8CA247C7256BB6B60006B376 /* Tagger */,
74 | );
75 | path = "Example Views";
76 | sourceTree = "";
77 | };
78 | 8C2FB91B256C826500BD5CD9 /* Toast */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 8C2FB91C256C828E00BD5CD9 /* ToastExampleView.swift */,
82 | 8C2FB920256C84F800BD5CD9 /* SubViews */,
83 | );
84 | path = Toast;
85 | sourceTree = "";
86 | };
87 | 8C2FB920256C84F800BD5CD9 /* SubViews */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 8C2FB921256C850500BD5CD9 /* ToastOverlayView.swift */,
91 | );
92 | path = SubViews;
93 | sourceTree = "";
94 | };
95 | 8CA247A2256BAE580006B376 = {
96 | isa = PBXGroup;
97 | children = (
98 | 8CA247AD256BAE580006B376 /* SwiftUIDemos */,
99 | 8CA247AC256BAE580006B376 /* Products */,
100 | );
101 | sourceTree = "";
102 | };
103 | 8CA247AC256BAE580006B376 /* Products */ = {
104 | isa = PBXGroup;
105 | children = (
106 | 8CA247AB256BAE580006B376 /* SwiftUIDemos.app */,
107 | );
108 | name = Products;
109 | sourceTree = "";
110 | };
111 | 8CA247AD256BAE580006B376 /* SwiftUIDemos */ = {
112 | isa = PBXGroup;
113 | children = (
114 | 8CA247AE256BAE580006B376 /* SwiftUIDemosApp.swift */,
115 | 8C2FB919256C677000BD5CD9 /* Example Views */,
116 | 8CA247B2256BAE5B0006B376 /* Assets.xcassets */,
117 | 8CA247B7256BAE5B0006B376 /* Info.plist */,
118 | 8CA247B4256BAE5B0006B376 /* Preview Content */,
119 | );
120 | path = SwiftUIDemos;
121 | sourceTree = "";
122 | };
123 | 8CA247B4256BAE5B0006B376 /* Preview Content */ = {
124 | isa = PBXGroup;
125 | children = (
126 | 8CA247B5256BAE5B0006B376 /* Preview Assets.xcassets */,
127 | );
128 | path = "Preview Content";
129 | sourceTree = "";
130 | };
131 | 8CA247C7256BB6B60006B376 /* Tagger */ = {
132 | isa = PBXGroup;
133 | children = (
134 | 8CA247BE256BAE6F0006B376 /* TaggerView.swift */,
135 | 8CA247C8256BB6BF0006B376 /* SubViews */,
136 | );
137 | path = Tagger;
138 | sourceTree = "";
139 | };
140 | 8CA247C8256BB6BF0006B376 /* SubViews */ = {
141 | isa = PBXGroup;
142 | children = (
143 | 8CA247C9256BB6DD0006B376 /* ErrorMessageView.swift */,
144 | 8CA247CE256BB7890006B376 /* TagEntryView.swift */,
145 | 8CA247D1256BB8D60006B376 /* TagListView.swift */,
146 | 8CA247D5256BB9300006B376 /* TagView.swift */,
147 | );
148 | path = SubViews;
149 | sourceTree = "";
150 | };
151 | 8CA247D9256C1CB80006B376 /* FilterGroupedList */ = {
152 | isa = PBXGroup;
153 | children = (
154 | 8C2FB909256C2F0D00BD5CD9 /* SubViews */,
155 | 8CA247DF256C1D580006B376 /* DummyListData.swift */,
156 | 8CA247DA256C1D260006B376 /* GroupedListView.swift */,
157 | );
158 | path = FilterGroupedList;
159 | sourceTree = "";
160 | };
161 | /* End PBXGroup section */
162 |
163 | /* Begin PBXNativeTarget section */
164 | 8CA247AA256BAE580006B376 /* SwiftUIDemos */ = {
165 | isa = PBXNativeTarget;
166 | buildConfigurationList = 8CA247BA256BAE5B0006B376 /* Build configuration list for PBXNativeTarget "SwiftUIDemos" */;
167 | buildPhases = (
168 | 8CA247A7256BAE580006B376 /* Sources */,
169 | 8CA247A8256BAE580006B376 /* Frameworks */,
170 | 8CA247A9256BAE580006B376 /* Resources */,
171 | );
172 | buildRules = (
173 | );
174 | dependencies = (
175 | );
176 | name = SwiftUIDemos;
177 | productName = SwiftUIDemos;
178 | productReference = 8CA247AB256BAE580006B376 /* SwiftUIDemos.app */;
179 | productType = "com.apple.product-type.application";
180 | };
181 | /* End PBXNativeTarget section */
182 |
183 | /* Begin PBXProject section */
184 | 8CA247A3256BAE580006B376 /* Project object */ = {
185 | isa = PBXProject;
186 | attributes = {
187 | LastSwiftUpdateCheck = 1220;
188 | LastUpgradeCheck = 1220;
189 | TargetAttributes = {
190 | 8CA247AA256BAE580006B376 = {
191 | CreatedOnToolsVersion = 12.2;
192 | };
193 | };
194 | };
195 | buildConfigurationList = 8CA247A6256BAE580006B376 /* Build configuration list for PBXProject "SwiftUIDemos" */;
196 | compatibilityVersion = "Xcode 9.3";
197 | developmentRegion = en;
198 | hasScannedForEncodings = 0;
199 | knownRegions = (
200 | en,
201 | Base,
202 | );
203 | mainGroup = 8CA247A2256BAE580006B376;
204 | productRefGroup = 8CA247AC256BAE580006B376 /* Products */;
205 | projectDirPath = "";
206 | projectRoot = "";
207 | targets = (
208 | 8CA247AA256BAE580006B376 /* SwiftUIDemos */,
209 | );
210 | };
211 | /* End PBXProject section */
212 |
213 | /* Begin PBXResourcesBuildPhase section */
214 | 8CA247A9256BAE580006B376 /* Resources */ = {
215 | isa = PBXResourcesBuildPhase;
216 | buildActionMask = 2147483647;
217 | files = (
218 | 8CA247B6256BAE5B0006B376 /* Preview Assets.xcassets in Resources */,
219 | 8CA247B3256BAE5B0006B376 /* Assets.xcassets in Resources */,
220 | );
221 | runOnlyForDeploymentPostprocessing = 0;
222 | };
223 | /* End PBXResourcesBuildPhase section */
224 |
225 | /* Begin PBXSourcesBuildPhase section */
226 | 8CA247A7256BAE580006B376 /* Sources */ = {
227 | isa = PBXSourcesBuildPhase;
228 | buildActionMask = 2147483647;
229 | files = (
230 | 8CA247CF256BB7890006B376 /* TagEntryView.swift in Sources */,
231 | 8C2FB90E256C600600BD5CD9 /* DepartmentListView.swift in Sources */,
232 | 8CA247DB256C1D260006B376 /* GroupedListView.swift in Sources */,
233 | 8C2FB922256C850500BD5CD9 /* ToastOverlayView.swift in Sources */,
234 | 8CA247CA256BB6DD0006B376 /* ErrorMessageView.swift in Sources */,
235 | 8CA247D2256BB8D60006B376 /* TagListView.swift in Sources */,
236 | 8CA247BF256BAE6F0006B376 /* TaggerView.swift in Sources */,
237 | 8C2FB911256C608600BD5CD9 /* PeopleListView.swift in Sources */,
238 | 8C2FB914256C616A00BD5CD9 /* SearchBarView.swift in Sources */,
239 | 8CA247AF256BAE580006B376 /* SwiftUIDemosApp.swift in Sources */,
240 | 8CA247D6256BB9300006B376 /* TagView.swift in Sources */,
241 | 8CA247E0256C1D580006B376 /* DummyListData.swift in Sources */,
242 | 8C2FB91D256C828E00BD5CD9 /* ToastExampleView.swift in Sources */,
243 | );
244 | runOnlyForDeploymentPostprocessing = 0;
245 | };
246 | /* End PBXSourcesBuildPhase section */
247 |
248 | /* Begin XCBuildConfiguration section */
249 | 8CA247B8256BAE5B0006B376 /* Debug */ = {
250 | isa = XCBuildConfiguration;
251 | buildSettings = {
252 | ALWAYS_SEARCH_USER_PATHS = NO;
253 | CLANG_ANALYZER_NONNULL = YES;
254 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
256 | CLANG_CXX_LIBRARY = "libc++";
257 | CLANG_ENABLE_MODULES = YES;
258 | CLANG_ENABLE_OBJC_ARC = YES;
259 | CLANG_ENABLE_OBJC_WEAK = YES;
260 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
261 | CLANG_WARN_BOOL_CONVERSION = YES;
262 | CLANG_WARN_COMMA = YES;
263 | CLANG_WARN_CONSTANT_CONVERSION = YES;
264 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
266 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
267 | CLANG_WARN_EMPTY_BODY = YES;
268 | CLANG_WARN_ENUM_CONVERSION = YES;
269 | CLANG_WARN_INFINITE_RECURSION = YES;
270 | CLANG_WARN_INT_CONVERSION = YES;
271 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
272 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
273 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
274 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
275 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
276 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
277 | CLANG_WARN_STRICT_PROTOTYPES = YES;
278 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
279 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
280 | CLANG_WARN_UNREACHABLE_CODE = YES;
281 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
282 | COPY_PHASE_STRIP = NO;
283 | DEBUG_INFORMATION_FORMAT = dwarf;
284 | ENABLE_STRICT_OBJC_MSGSEND = YES;
285 | ENABLE_TESTABILITY = YES;
286 | GCC_C_LANGUAGE_STANDARD = gnu11;
287 | GCC_DYNAMIC_NO_PIC = NO;
288 | GCC_NO_COMMON_BLOCKS = YES;
289 | GCC_OPTIMIZATION_LEVEL = 0;
290 | GCC_PREPROCESSOR_DEFINITIONS = (
291 | "DEBUG=1",
292 | "$(inherited)",
293 | );
294 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
295 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
296 | GCC_WARN_UNDECLARED_SELECTOR = YES;
297 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
298 | GCC_WARN_UNUSED_FUNCTION = YES;
299 | GCC_WARN_UNUSED_VARIABLE = YES;
300 | IPHONEOS_DEPLOYMENT_TARGET = 14.2;
301 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
302 | MTL_FAST_MATH = YES;
303 | ONLY_ACTIVE_ARCH = YES;
304 | SDKROOT = iphoneos;
305 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
306 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
307 | };
308 | name = Debug;
309 | };
310 | 8CA247B9256BAE5B0006B376 /* Release */ = {
311 | isa = XCBuildConfiguration;
312 | buildSettings = {
313 | ALWAYS_SEARCH_USER_PATHS = NO;
314 | CLANG_ANALYZER_NONNULL = YES;
315 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
317 | CLANG_CXX_LIBRARY = "libc++";
318 | CLANG_ENABLE_MODULES = YES;
319 | CLANG_ENABLE_OBJC_ARC = YES;
320 | CLANG_ENABLE_OBJC_WEAK = YES;
321 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
322 | CLANG_WARN_BOOL_CONVERSION = YES;
323 | CLANG_WARN_COMMA = YES;
324 | CLANG_WARN_CONSTANT_CONVERSION = YES;
325 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
326 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
327 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
328 | CLANG_WARN_EMPTY_BODY = YES;
329 | CLANG_WARN_ENUM_CONVERSION = YES;
330 | CLANG_WARN_INFINITE_RECURSION = YES;
331 | CLANG_WARN_INT_CONVERSION = YES;
332 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
333 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
334 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
335 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
336 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
337 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
338 | CLANG_WARN_STRICT_PROTOTYPES = YES;
339 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
340 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
341 | CLANG_WARN_UNREACHABLE_CODE = YES;
342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
343 | COPY_PHASE_STRIP = NO;
344 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
345 | ENABLE_NS_ASSERTIONS = NO;
346 | ENABLE_STRICT_OBJC_MSGSEND = YES;
347 | GCC_C_LANGUAGE_STANDARD = gnu11;
348 | GCC_NO_COMMON_BLOCKS = YES;
349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
351 | GCC_WARN_UNDECLARED_SELECTOR = YES;
352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
353 | GCC_WARN_UNUSED_FUNCTION = YES;
354 | GCC_WARN_UNUSED_VARIABLE = YES;
355 | IPHONEOS_DEPLOYMENT_TARGET = 14.2;
356 | MTL_ENABLE_DEBUG_INFO = NO;
357 | MTL_FAST_MATH = YES;
358 | SDKROOT = iphoneos;
359 | SWIFT_COMPILATION_MODE = wholemodule;
360 | SWIFT_OPTIMIZATION_LEVEL = "-O";
361 | VALIDATE_PRODUCT = YES;
362 | };
363 | name = Release;
364 | };
365 | 8CA247BB256BAE5B0006B376 /* Debug */ = {
366 | isa = XCBuildConfiguration;
367 | buildSettings = {
368 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
369 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
370 | CODE_SIGN_STYLE = Automatic;
371 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUIDemos/Preview Content\"";
372 | DEVELOPMENT_TEAM = NWHDL7X5B3;
373 | ENABLE_PREVIEWS = YES;
374 | INFOPLIST_FILE = SwiftUIDemos/Info.plist;
375 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
376 | LD_RUNPATH_SEARCH_PATHS = (
377 | "$(inherited)",
378 | "@executable_path/Frameworks",
379 | );
380 | PRODUCT_BUNDLE_IDENTIFIER = com.alexhay.SwiftUIDemos;
381 | PRODUCT_NAME = "$(TARGET_NAME)";
382 | SWIFT_VERSION = 5.0;
383 | TARGETED_DEVICE_FAMILY = "1,2";
384 | };
385 | name = Debug;
386 | };
387 | 8CA247BC256BAE5B0006B376 /* Release */ = {
388 | isa = XCBuildConfiguration;
389 | buildSettings = {
390 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
391 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
392 | CODE_SIGN_STYLE = Automatic;
393 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUIDemos/Preview Content\"";
394 | DEVELOPMENT_TEAM = NWHDL7X5B3;
395 | ENABLE_PREVIEWS = YES;
396 | INFOPLIST_FILE = SwiftUIDemos/Info.plist;
397 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
398 | LD_RUNPATH_SEARCH_PATHS = (
399 | "$(inherited)",
400 | "@executable_path/Frameworks",
401 | );
402 | PRODUCT_BUNDLE_IDENTIFIER = com.alexhay.SwiftUIDemos;
403 | PRODUCT_NAME = "$(TARGET_NAME)";
404 | SWIFT_VERSION = 5.0;
405 | TARGETED_DEVICE_FAMILY = "1,2";
406 | };
407 | name = Release;
408 | };
409 | /* End XCBuildConfiguration section */
410 |
411 | /* Begin XCConfigurationList section */
412 | 8CA247A6256BAE580006B376 /* Build configuration list for PBXProject "SwiftUIDemos" */ = {
413 | isa = XCConfigurationList;
414 | buildConfigurations = (
415 | 8CA247B8256BAE5B0006B376 /* Debug */,
416 | 8CA247B9256BAE5B0006B376 /* Release */,
417 | );
418 | defaultConfigurationIsVisible = 0;
419 | defaultConfigurationName = Release;
420 | };
421 | 8CA247BA256BAE5B0006B376 /* Build configuration list for PBXNativeTarget "SwiftUIDemos" */ = {
422 | isa = XCConfigurationList;
423 | buildConfigurations = (
424 | 8CA247BB256BAE5B0006B376 /* Debug */,
425 | 8CA247BC256BAE5B0006B376 /* Release */,
426 | );
427 | defaultConfigurationIsVisible = 0;
428 | defaultConfigurationName = Release;
429 | };
430 | /* End XCConfigurationList section */
431 | };
432 | rootObject = 8CA247A3256BAE580006B376 /* Project object */;
433 | }
434 |
--------------------------------------------------------------------------------