[SWIFT] iOS app development: Timer app (9. Customize the color of the progress bar)

スクリーンショット 2020-10-28 11.20.58.png

article

The points for creating a timer app are posted in multiple articles. In this article, I'll show you how to apply your own gradient color to a progress bar and change its hue over time.

environment

Git repository

You can see the sample code from the URL of the Git repository below. https://github.com/msnsk/Qiita_Timer.git

procedure

  1. Consider what color the progress bar should be
  2. Create a property in ProgressBarView to store the changing hue (Hue) value.
  3. Create a method to generate a gradient in ProgressBarView
  4. Add a method to the color argument of the .stroke modifier in Circle () on the progress bar.
  5. Add a .onReceive modifier to ProgressBarView to keep the progress bar color changing

1. Consider what color the progress bar should be

Think about how to beautifully express the color of the progress bar. This may vary from person to person. In my case, I thought the following two points were important. One is to display in gradation instead of single color. The other is that the color changes over time. Implement these two to improve the visual impression of your app.

2. Create a property in ProgressBarView to store the changing hue (Hue) value.

You need at least two colors to create a gradient. I will make these two colors by myself.

In Swift, there are several ways to specify a color. It also meets common color standards. For example, RGB, CMYK, HSB.

Simply put, RGB expresses color by the amount of each of the three primary colors of red, green, and blue light. If all three colors are 100%, it will be white, and if it is 0%, it will be black.

CMYK is the three primary colors + K: Black, and contrary to RGB, all three colors are 100% black and 0% white. I think it's easy to understand if you imagine an inkjet printer.

And HSB represents color by Hue, Saturation, and Brightness. Hue indicates the number of colors, Saturation is the degree of hue (0 is monotone), and Brightness is 100% white, so the color is not muddy.

Considering that the color is always changed in the end, if it is HSB, it seems that it is easy to adjust if only the Hue value is changed and Saturation and Brightness are fixed, so here we adopt the color specification in HSB. To do.

Now let's add two colors to the ProgressBarView to store the hue (Hue). Let's call it customHueA and customHueB, respectively.

When specifying a color in HSB, Color () requires the following arguments. Each data type is CGFloat, with a minimum value of 0.0 and a maximum value of 1.0.

Color(hue:, saturation:, brightness:)

Therefore, for the time being, substitute 0.5 and 0.3 as the initial values of the newly added Hue property values. Keep the colors relatively close, with 0.5 being turquoise and 0.3 being yellowish green.

For example, if it is 0.0 and 0.5, the hues will be opposite and the color will tend to be muddy in the middle when the final gradation is made. Therefore, the difference in hue is limited to 0.2 here. By the way, Hue 0.0 and Hue 1.0 go around and have the same color.

ProgressBarView


struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager
    
    @State var costomHueA = 0.5
    @State var costomHueB = 0.3

    var body: some View {
        //(abridgement)
    }
}

3. Create a method to generate a gradient in ProgressBarView

Next, from the two properties that store the Hue value, we will create a method that creates a gradient suitable for a circular progress bar.

I named the method makeGradientColor.

The arguments are the arguments hueA and hueB that take double type data. I plan to put the property I created earlier here.

And the data type of the return value is AngularGradient with a conical gradient. By the way, there are other Swift gradients such as LinearGradient and RadialGradient. Angular Gradient is a gradation that is applied in the direction of going around the circle.

func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient {}

Next, write inside the {} of the makeGradientColor method. First, in the method {}, first prepare a property that creates the two colors required for the gradient.

ColorA creates a color from the value of the argument hueA. This will contain the ProgressBarView property costomHueA. Similarly, ColorB creates a color from the value of the argument hueB. CostomHueB will be included here.

Both ColorA and ColorB have the same number for saturation and brightness. It's a little higher again, and the brightness is much higher.

func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient {
let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9)
let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9)
}

And finally, make a conical gradation from the two colors you made. We are creating an instance of the AngularGradient structure, where we put the initializer .init () in the argument gradient, and in that argument we put the color from the beginning to the end of the gradient in the Array.

If you do the following here, the colors will be clearly separated where the start and end points of the circle overlap.

[colorA, colorB]

Therefore, apply a gradation from colorB to colorA again as shown below.

[colorA, colorB, colorA]

You can also place colors in the middle to make finer adjustments. For example, it is as follows. Here, I will limit the gradation to two colors.

[colorA, colorB, colorC, colorD, colorA]

func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient {
let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9)
let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9)
let gradient = AngularGradient(gradient: .init(colors: [colorA, colorB, colorA]), center: .center, startAngle: .zero, endAngle: .init(degrees: 360))
return gradient
}

The method is described under body {}.

ProgressBarView


struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager
    
    @State var costomHueA = 1.0
    @State var costomHueB = 0.5

    var body: some View {
        //(abridgement)
    }

    func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient {
        let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9)
        let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9)
        let gradient = AngularGradient(gradient: .init(colors: [colorA, colorB, colorA]), center: .center, startAngle: .zero, endAngle: .init(degrees: 360))
        return gradient
    }
}

4. Add a method to the color argument of the .stroke modifier in Circle () on the progress bar.

Previously, the color of the circle in the progress bar was specified as a single color cyan in the argument of the .stroke modifier.

.stroke(Color(.cyan), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))

Now, put the method you just created as an argument instead of Color (.cyan). In the method arguments hueA and hueB, put the properties costomHueA and costomHueB prepared earlier, respectively.

.stroke(self.makeGradientColor(hueA: costomHueA, hueB: costomHueB), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))

The whole code looks like this:

ProgressBarView


struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager
    
    @State var costomHueA = 1.0
    @State var costomHueB = 0.5

    var body: some View {
        ZStack {
            //(Circle for background omitted)
            
            //Circle for progress bar
            Circle()
                .trim(from: 0, to: CGFloat(self.timeManager.duration / self.timeManager.maxValue))
                //Apply the gradient color generated by the method
                .stroke(self.makeGradientColor(hueA: costomHueA, hueB: costomHueB), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
                .scaledToFit()
                //Set the start position of the contour line to the 12 o'clock direction
                .rotationEffect(Angle(degrees: -90))
                .padding(15)
        }
    }

    func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient {
        let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9)
        let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9)
        let gradient = AngularGradient(gradient: .init(colors: [colorA, colorB, colorA]), center: .center, startAngle: .zero, endAngle: .init(degrees: 360))
        return gradient
    }
}

5. Add a .onReceive modifier to ProgressBarView to keep the progress bar color changing

In step 4, the color of the progress bar can be displayed in gradation by specifying it from the hue of the property.

In addition, the onReceive modifier triggers the timer property (Timer.publish () method) of the TimeManager class to change the values of the hue properties costomHueA and costomHueB every 0.05 seconds to change the color of the progress bar. I will keep changing.

Add the hue value by 0.005 every 0.05 seconds. If you want to change the color more slowly, 0.001 is good, and if you want to change the color more slowly, 0.01 is good. If you raise the value any further, the color will just flicker and the movement will hurt your eyes.

Also, since 1.0 is the maximum value for hue, when it reaches 1.0, describe it with an if statement so that it returns to 0.0.

.onReceive(timeManager.timer) { _ in
    self.costomHueA += 0.005
    if self.costomHueA >= 1.0 {
        self.costomHueA = 0.0
    }

Write this for costomHueB in the same way. The whole code looks like this:

ProgressBarView


struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager
    
    @State var costomHueA = 0.5
    @State var costomHueB = 0.3
    
    var body: some View {
        ZStack {
            //Circle for background
            Circle()
                .stroke(Color(.darkGray), style: StrokeStyle(lineWidth: 20))
                .scaledToFit()
                .padding(15)
            
            //Circle for progress bar
            Circle()
                .trim(from: 0, to: CGFloat(self.timeManager.duration / self.timeManager.maxValue))
                //Apply the gradient color generated by the method
                .stroke(self.makeGradientColor(hueA: costomHueA, hueB: costomHueB), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
                .scaledToFit()
                //Set the start position of the contour line to the 12 o'clock direction
                .rotationEffect(Angle(degrees: -90))
                .padding(15)
        }
        //Every 0.Activated every 05 seconds
        .onReceive(timeManager.timer) { _ in
            self.costomHueA += 0.005
            if self.costomHueA >= 1.0 {
                self.costomHueA = 0.0
            }
            self.costomHueB += 0.005
            if self.costomHueB >= 1.0 {
                self.costomHueB = 0.0
            }
        }
    }
    
    func makeGradientColor(hueA: Double, hueB: Double) -> AngularGradient {
        let colorA = Color(hue: hueA, saturation: 0.75, brightness: 0.9)
        let colorB = Color(hue: hueB, saturation: 0.75, brightness: 0.9)
        let gradient = AngularGradient(gradient: .init(colors: [colorA, colorB, colorA]), center: .center, startAngle: .zero, endAngle: .init(degrees: 360))
        return gradient
    }
}

Now you have a circular progress bar with a beautiful gradation and a constantly changing hue.

Recommended Posts

iOS app development: Timer app (9. Customize the color of the progress bar)
iOS app development: Timer app (8. Implementation of progress bar)
iOS app development: Timer app (6. Creation of setting screen)
iOS app development: Timer app (summary)
iOS app development: Timer app (7. Implementation of alarm sound selection)
iOS app development: Timer app (5. Implementation of alarm and vibration)
iOS app development: Timer app (4. Countdown implementation)
iOS app development: Timer app (1. Timer time setting)
iOS app development: Timer app (10. Create animation)
iOS app development: Timer app (3. Start / Stop button, Reset button)
Review the multilingual support (i18n) of the ios app in 3 minutes
Learn how to customize the Navigation Bar from the sample app
I thought about the strategy of introducing Combine in iOS development
[Swift] The color of the Navigation Bar is different (lighter) from the specified color.
About the basics of Android development
Complete self-study IOS app development diary
iOS App Development Skill Roadmap (Introduction)
I stumbled upon the development and publication of my first iPhone app
Utilization of swift video progress bar / UIGestureRecognizerDelegate
[Eclipse] Change the color of the vertical ruler
Bootstrap4 Change the color of the hamburger menu
[Swift] Change the color of SCN Node