DEV Community

Avelyn Hyunjeong Choi
Avelyn Hyunjeong Choi

Posted on

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

Top comments (0)