UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 46, challenge ColorCycling: not sure what is meant, two solutions follow. Comments invited.

Forums > 100 Days of SwiftUI

In the third challenge for the Drawing app, the following is asked:

Create a ColorCyclingRectangle shape that is the rectangular cousin of ColorCyclingCircle, allowing us to control the position of the gradient using one or more properties.

I was not sure what exactly is meant. Do we literally have to use a Gradient? The original solution did not, but worked with insets, all stroked with a different color.

Also, we are asked to use a rectangle, but is this just the surrounding shape or do the different colors also need to have rectangular shape?

In the end, I decided to make two solutions. The user can switch using a toggle.

The first is analogous to the given solution in that it is circular, however embedded in a (rounded) rectangular shape and implemented using a RadialGradient. The center can be moved through sliders for X and Y. This one was not too difficult.

The second was to have (rounded) rectangular shapes everywhere, getting smaller and smaller and so that they "disappear" in a point. This point can also be moved. At first I thought to use inset(by:), but that appears not to be possible in SwiftUI: one cannot have separate values for the four directions. And I need to, in order to be able to move the center. So I had to forego inset(by:), and size and position all rectangles explicitly.

I did consider creating my own inset(by insets: EdgeInsets) modifier, but found it not necessary, and probably more complicated than necessary, once I found out how simple it is to size and position a rectangle.

It cost me quite some time to arrive at this solution and I had a few false starts, but in the end it does not seem too complicated. Any comments or suggestions are welcome.

import SwiftUI

struct ColorsView: View {
    @State private var colorCycle = 0.0
    @State private var locX = 50.0
    @State private var locY = 50.0
    @State private var useGradient = true

    let baseCornerRadius = 25.0

    var body: some View {
        VStack {
            Toggle("Use gradient?", isOn: $useGradient)
            ZStack {
                GeometryReader { geo in
                    let w = geo.size.width
                    let h = geo.size.height
                    let m = min(w,h)
                    let centerX = locX * w / 100
                    let centerY = locY * h / 100
                    VStack {
                        if useGradient {
                            let center = UnitPoint(x: centerX / w, y: centerY / h)
                            RoundedRectangle(cornerRadius: baseCornerRadius)
                                .fill(
                                    RadialGradient(gradient: Gradient(colors: colors(steps: Int(m), amount: colorCycle)), center: center, startRadius: 0, endRadius: m/2))
                        } else {
                            ZStack {
                                ForEach(0..<Int(m), id: \.self) { value in
                                    let v = CGFloat(value)
                                    let cornerRadius = baseCornerRadius * (m - Double(value)) / m
                                    RoundedRectangle(cornerRadius: cornerRadius)
                                        .size(width: w-(v*w/m), height: h-(v*h/m))
                                        .offset(x: v*(centerX/m), y: v*(centerY/m))
                                        .stroke(color(for: value, amount: colorCycle, steps: Int(m)), lineWidth: 2)

                                }
                            }
                        }
                    }
                }
            }

            Text("Color cycle")
            Slider(value: $colorCycle)

            Text("centerX")
            Slider(value: $locX, in: 5...95, step: 1)

            Text("centerY")
            Slider(value: $locY, in: 5...95, step: 1)
        }
    }

    func color(for value: Int, amount: Double, steps: Int) -> Color {
        var targetHue = Double(value) / Double(steps) + amount

        targetHue = targetHue.truncatingRemainder(dividingBy: 1)

        return Color(hue: targetHue, saturation: 1, brightness: 1)
    }

    func colors(steps: Int, amount: Double) -> [Color] {
        var colors = [Color]()

        for i in 0..<steps {
            colors.append(color(for: i, amount: amount, steps: steps))
        }

        return colors
    }
}

2      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.