DEV Community

Avelyn Hyunjeong Choi
Avelyn Hyunjeong Choi

Posted on

3

SwiftUI with State

In this blog, I will use SwiftUI with concept of state and binding to pass values.

Final Screen
Image description

// ColorParser.swift

import Foundation
// get hex code, return rgb value
func getRGBAColor(rgba: String) -> (UInt8,UInt8,UInt8,UInt8) {
    var red: UInt8 = 0
    var green: UInt8 = 0
    var blue: UInt8 = 0
    var alpha: UInt8 = 255

    var hexColor = rgba //String(rgba[rgba.startIndex...])

    // 3 or 4 characters are treated as 3 and 4 digit CSS color values
    if hexColor.count == 3 || hexColor.count == 4 {
        var colorCharacterArray = Array(hexColor)
        colorCharacterArray.insert(colorCharacterArray[0], at: 1)
        colorCharacterArray.insert(colorCharacterArray[2], at: 3)
        colorCharacterArray.insert(colorCharacterArray[4], at: 5)
        if colorCharacterArray.count == 7 { // if we started with a 4 digit CSS hex color
            colorCharacterArray.insert(colorCharacterArray[6], at: 7 )
        }
        print(colorCharacterArray)
        hexColor = String(colorCharacterArray)
    }

    let scanner = Scanner(string: hexColor)
    var hexNumber: UInt64 = 0

    // 8 characters are treated as 8 digit RRGGBBAA values
    if hexColor.count == 8 {
        if scanner.scanHexInt64(&hexNumber) {
            red = UInt8((hexNumber & 0xff000000) >> 24)
            green = UInt8((hexNumber & 0x00ff0000) >> 16)
            blue = UInt8((hexNumber & 0x0000ff00) >> 8)
            alpha = UInt8(hexNumber & 0x000000ff)
        }
        // 6 characters are treated as 6 digit RRGGBB values
    } else if hexColor.count == 6 { // assume no alpha value

        if scanner.scanHexInt64(&hexNumber) {
            red = UInt8((hexNumber & 0xff0000) >> 16)
            green = UInt8((hexNumber & 0x00ff00) >> 8)
            blue = UInt8((hexNumber & 0x0000ff))
            alpha = 255 // set to max
        }
    }
    return (red,green,blue,alpha)
}
Enter fullscreen mode Exit fullscreen mode

// ColorModel.swift

import Foundation

struct ColorModel {
    var red: Double
    var green: Double
    var blue: Double
    var alpha: Double

    // computational property
    var hex: String {
        get {
            // 02 = minimum 2 characters
            String(format: "%02X%02X%02X%02X", Int(red), Int(green), Int(blue), Int(alpha))
        }

        set {
            // newValue = result from get
            let rgba = getRGBAColor(rgba: newValue) // retruns tuple (red, green, blue, alpha)

            self.red = Double(rgba.0)
            self.green = Double(rgba.1)
            self.blue = Double(rgba.2)
            self.alpha = Double(rgba.3)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Create multiple Views
// TextView

import SwiftUI

struct TextView: View {
    @Binding var hex: String
    var applyActions: () -> Void

    var body: some View {
        HStack {
            TextField("Enter Hex Color", text: $hex)

            Button(action: applyActions) {
                Text("Apply")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

// ColorPreviewView

import SwiftUI

struct ColorPreviewView: View {
    @Binding var red: Double
    @Binding var green: Double
    @Binding var blue: Double
    @Binding var alpha: Double

    var hex: String {
        String(format: "%02X%02X%02X%02X", Int(red), Int(green), Int(blue), Int(alpha))
    }

    var body: some View {
        VStack {
            Rectangle()
            // look out $ sign is missing why?
            // we need it for binding. when do we need to bind? we bind two when you want to pass something
            // are we passing something in here? we don't need to use $ here
                .fill(Color(red: red/255.0, green: green/255.0, blue: blue/255.0, opacity: alpha/255.0))
            Text("Hex: \(hex)")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

// ColorSliderView

import SwiftUI

struct ColorSliderView: View {
    @Binding var value: Double // holds binding variable (props in react) -> pass the binding to parent view (SliderView)
    var colorName: String

    var sliderColor: Color {
        switch colorName {
        case "Red":
            return Color.red
        case "Green":
            return Color.green
        case "Blue":
            return Color.blue
        case "Alpha":
            return Color.black
        default:
            return Color.primary
        }
    }

    var body: some View {
        VStack {
            // provide value with $
            // slider can update the value as well
            // bind state to the slider(UI) -> slider will update value whenever you ask to change it
            Slider(value: $value, in: 0...255, step: 1)

            // apply color to the slider
                .accentColor(sliderColor)

            HStack {
                Text("\(colorName):")
                Text(String(format: "%.0f", value))
            }
            .foregroundColor(sliderColor)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

// SliderView.swift

import SwiftUI

// to provide four sliders in the view
// building a parent view of ColorSliderView
struct SliderVIew: View {
    @Binding var red: Double
    @Binding var green: Double
    @Binding var blue: Double
    @Binding var alpha: Double

    var body: some View {
        VStack {
            ColorSliderView(value: $red, colorName: "Red")
            ColorSliderView(value: $green, colorName: "Green")
            ColorSliderView(value: $blue, colorName: "Blue")
            ColorSliderView(value: $alpha, colorName: "Alpha")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Combine all views we created in ContentView
// ContentView

import SwiftUI

struct ContentView: View {
    // initial state
    @State private var red: Double = 127.5
    @State private var green: Double = 127.5
    @State private var blue: Double = 127.5
    @State private var alpha: Double = 255.0
    @State private var hex: String = "7F7F7FFF"

    var body: some View {
        // now we have to combine all the views to main in ContentView
        VStack {
            TextView(hex: $hex, applyActions: applyHex)

            // binding: bingding variable changes -> view changes automatically
            ColorPreviewView(red: $red, green: $green, blue: $blue, alpha: $alpha)

            SliderVIew(red: $red, green: $green, blue: $blue, alpha: $alpha)

            Button(action: resetValue) {
                Text("Reset")
            }
            .padding()
        }
        .padding()
    }

    // method for applyActions for Stack above
    private func applyHex() {
        let rgba = getRGBAColor(rgba: hex)

        red = Double(rgba.0)
        green = Double(rgba.1)
        blue = Double(rgba.2)
        alpha = Double(rgba.3)
    }

    private func resetValue() {
        red = 127.5
        green = 127.5
        blue = 127.5
        alpha = 127.5
        hex = "7F7F7FFF"
    }
}
Enter fullscreen mode Exit fullscreen mode

Demo

Image description

When 'reset' button is clicked, it goes back to the initial state.

Image description

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more