BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

SOLVED: How do I share a binding data to different views?

Forums > SwiftUI

I've been figuring out how to share the color data to other views but still haven't been able to do it. What I wanna do here is by selecting your prefered color in the color setting view, for example, the color of the background of your content view or the color of the button in your another view also changes into the color you selected. I'd really appreciate your help.

 import SwiftUI

    struct ColorData {
        private var ColorKey = "ColorKey"
        private let userDefaults = UserDefaults.standard

        func save(color: Color) {
            let color = UIColor(color).cgColor

            if let components = color.components {
                userDefaults.set(components, forKey: ColorKey)
                print("Color saved.")
            }
        }

        func loadColor() -> Color {
            guard let array = userDefaults.object(forKey: ColorKey) as?
                    [CGFloat] else { return Color.cyan }

            let color = Color(.sRGB, red: array[0], green: array[1], blue: array[2], opacity: array[3])

            print(color)
            print("Color loaded.")
            return color
        }
    }

    class colorTheme: ObservableObject {
        @Published var cc: Color = Color.cyan
    }

    struct ColorSetting: View {
        @StateObject var choosenColor = colorTheme()
        var colorData = ColorData()

        var body: some View {
            ZStack {
                choosenColor.cc
                    .edgesIgnoringSafeArea(.all)
                Color.white

                VStack {
                    Text("Color Theme")
                        .foregroundColor(choosenColor.cc)
                        .font(.largeTitle)
                        .padding(.top, -180)

                    ColorPicker("Select a color", selection: $choosenColor.cc, supportsOpacity: true)
                        .padding()
                        .cornerRadius(10)
                        .font(.system(size: 20))
                        .padding(50)

                    Button("Save") {
                        colorData.save(color: choosenColor.cc)
                    }
                    .padding()
                    .background(choosenColor.cc)
                    .font(.system(size: 20))
                    .cornerRadius(10)
                    .foregroundColor(.white)
                    .padding()
                    .onAppear {
                        choosenColor.cc = colorData.loadColor()
                    }
                }
            }
        }
    }

   

Hi,

I would suggest you use @EnvironmentObject (https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views). I’ve changed your code a little bit so that ColorSetting appears as a sheet. 

ColorTheme and ColorData don’t need to be changed. 

struct ColorData {
    private var ColorKey = "ColorKey"
    private let userDefaults = UserDefaults.standard

    func save(color: Color) {
        let color = UIColor(color).cgColor

        if let components = color.components {
            userDefaults.set(components, forKey: ColorKey)
            print("Color saved.")
        }
    }

    func loadColor() -> Color {
        guard let array = userDefaults.object(forKey: ColorKey) as?
                [CGFloat] else { return Color.cyan }

        let color = Color(.sRGB, red: array[0], green: array[1], blue: array[2], opacity: array[3])

        print(color)
        print("Color loaded.")
        return color
    }
}

class ColorTheme: ObservableObject {
    @Published var cc: Color = Color.cyan
}

Move the initialization of ColorTheme from ColorSetting to the @main structure. 

@main
struct ShareColorApp: App {
    @StateObject var chosenColor = ColorTheme() // Initialize ColorTheme right at the start of the app

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(chosenColor) // Place the object into the environment
        }
    }
}

Use @EnvironmentObject to access the color. 

struct ColorSetting: View {
    @EnvironmentObject var chosenColor: ColorTheme // Get the object from the environment
    @Binding var showView: Bool // Add this to dismiss the view
    var colorData = ColorData()

    var body: some View {
        ZStack {
            chosenColor.cc
                .edgesIgnoringSafeArea(.all)
            Color.white

            VStack {
                Text("Color Theme")
                    .foregroundColor(chosenColor.cc)
                    .font(.largeTitle)
                    .padding(.top, -180)

                ColorPicker("Select a color", selection: $chosenColor.cc, supportsOpacity: true)
                    .padding()
                    .cornerRadius(10)
                    .font(.system(size: 20))
                    .padding(50)

                Button("Save") {
                    colorData.save(color: chosenColor.cc)
                    showView = false // Dismiss the view
                }
                .padding()
                .background(chosenColor.cc)
                .font(.system(size: 20))
                .cornerRadius(10)
                .foregroundColor(.white)
                .padding()
                .onAppear {
                    chosenColor.cc = colorData.loadColor()
                }
            }
        }
    }
}

struct ColorSetting_Previews: PreviewProvider {
    static var previews: some View {
        ColorSetting(showView: .constant(false))
    }
}
struct ContentView: View {
    @EnvironmentObject var chosenColor: ColorTheme // Get the object from the environment
    @State private var showColorSetting = false // Add this to access ColorSetting

    var body: some View {
        NavigationStack {
            VStack {
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundColor(chosenColor.cc) // Use the color
                Text("Hello, world!")
            }
            .toolbar(content: {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button {
                        showColorSetting = true
                    } label: {
                        Image(systemName: "gearshape.fill")
                    }
                }
            })
            .sheet(isPresented: $showColorSetting, content: {
                ColorSetting(showView: $showColorSetting)
            })
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static let chosenColor = ColorTheme() // Necessary for the preview to work
    static var previews: some View {
        ContentView()
            .environmentObject(chosenColor)  // Necessary for the preview to work
    }
}

This code doesn’t load the saved color automatically, if you wish to do so you could write something like this:

@main
struct ShareColorApp: App {
    @StateObject var chosenColor = ColorTheme()
    var colorData = ColorData()
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    chosenColor.cc = colorData.loadColor()
                }
                .environmentObject(chosenColor)
        }
    }
}

Hope this helps!

   

@sodapool Thank you so much for your answer, it absolutely helped :)! Now my color setting is working greatly, I'm happy with the result! Btw, I have another question if you could possibly answer. I only have a vague understanding on how the @main class(?) works, that is in the file that we always have every time we create a project. I would appreciate it if you could provide me some explanation on how it actually works :)

   

I’m glad I could help.

To answer your question about @main:

Every program needs an entry point, i.e. a point where the execution begins.

Take a playground as an example. Its code is executed from top to bottom, that’s because a playground consists of one main playground file and this file is the entry point. If there’s just one file, there’s no confusion about where to start.

Apps consist of multiple files, it’s therefore necessary to mark the entry point. That’s the job of @main.

@main itself is an attribute. Attributes provide additional information about a type. In our case @main marks the structure as providing the program’s entry point.

(I previously wrote @main class which is wrong, it’s a structure. I’ve edited my answer.)

@main on its own only indicates the location of the entry point, it doesn’t initialize or run the app. That’s done by a static method called main() and every type annotated with the @main attribute is required to declare it. We don’t need to do this, because the App protocol to which the structure conforms to does. The function takes care of the whole application startup. We however, don’t know a lot about it, because the SwiftUI framework isn’t open source and this is all we get to see.

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension App {

    /// Initializes and runs the app.
    ///
    /// If you precede your ``SwiftUI/App`` conformer's declaration with the
    /// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626)
    /// attribute, the system calls the conformer's `main()` method to launch
    /// the app. SwiftUI provides a
    /// default implementation of the method that manages the launch process in
    /// a platform-appropriate way.
    @MainActor public static func main()
}

1      

@sodapool I really appreciate your clear answer :) Now I get how @main structure works and why it's necessary to mark an entry point :)

Thank you so much for your time to answer the question!

   

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.