[SWIFT] iOS app development: Timer app (1. Timer time setting)

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

Contents

The points for creating a timer app are posted in multiple articles. In this article, I will write about creating a View that sets the maximum time to count down to the timer.

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. Create TimeManager class
  2. Create a property to store the countdown time in TimeManager
  3. Create PickerView
  4. Place the Picker in the PickerView
  5. Create a method to reflect the time set in TimeManager
  6. Create a method to display the remaining time during the countdown in TimeManager

1. Create TimeManager class

Create a file called TimeManager.swift and create a Class with the same name. Assuming later, we will apply a protocol called ObservableObject. ObservableObject Simply put, is the protocol needed to keep an eye on the properties of variables whose values change. If you prepare a property (var) prefixed with the keyword @Published (Property Wrapper) in a class to which this protocol is applied, its value can always be referenced from other views.

TimeManager.swift


import SwiftUI

class TimeManager: ObservableObject {

}

2. Create a property to store the countdown time in TimeManager

Prepare the following five properties to set the countdown time.

--A property that stores hourly settings --A property that stores the setting value in minutes --A property that stores the setting value in seconds --The property of the maximum time before the start of the countdown, which is the sum of the above three --A property whose value continues to decrease during the countdown with the total value as the maximum value.

I want the value of each property to always be visible to other views, so I put the @Published property wrapper in front of var.

Since hours, minutes, and seconds are obtained from Picker as Int type, specify Int as the data type. On the other hand, specify Double for the remaining time and the maximum time. This is to make the values of these properties more accessible later. When programming using a variable, an error will occur if the data types of the variable are not unified.

Each property must have a default value, so leave it at 0.

TimeManager.swift


class TimeManager: ObservableObject {
    //Set with Picker"time"Variable to store
    @Published var hourSelection: Int = 0
    //Set with Picker"Minutes"Variable to store
    @Published var minSelection: Int = 0
    //Set with Picker"Seconds"Variable to store
    @Published var secSelection: Int = 0
    //Countdown time remaining
    @Published var duration: Double = 0
    //Maximum time before the countdown starts
    @Published var maxValue: Double = 0
}

Furthermore, I want to change the time display format according to the length of the set countdown time (for example, "02:30:00" if set to 2 hours 30 minutes, "05:00" if set to 5 minutes, if set to 30 seconds. I want to display "30"), so I will create an enum.

Create a new file called Data.swift and create an enum called TimeFormat.

Data.swift



import SwiftUI

enum TimeFormat {
    case hr
    case min
    case sec
}

Then, prepare a property that specifies the enum of TimeFormat created in the TimeManager class as the type so that the display format can be specified. The default value should be min for now.

TimeManager.swift


class TimeManager: ObservableObject {
    //(Omitting the property created earlier)
    //Time display format that changes depending on the set time of 1 hour or more, less than 1 hour, 1 minute or more, less than 1 minute, 1 second or more
    @Published var displayedTimeFormat: TimeFormat = .min
}

3. Create PickerView

Create a new file called PickerView.swift and create a View type Struct (structure) with the same name.

PickerView.swift


import SwiftUI

struct PickerView: View {
    var body: some View {
    }
}

First, create the required properties. Now, create an instance of the TimeManager class you created earlier. By prefixing the @EnvironmentObject keyword (Property Wrapper), you can always refer to the value of the changing variable of TimeManager. That is, the @Published property in the ObservableObject protocol-applied class and the @EnvironmentObject property in the View protocol-applied Struct are paired and always synchronized.

PickerView.swift


struct PickerView: View {
    //Create an instance of TimeManager
    @EnvironmentObject var timeManager: TimeManager
    //Device screen width
    let screenWidth = UIScreen.main.bounds.width
    //Device screen height
    let screenHeight = UIScreen.main.bounds.height
    //Configurable time unit number: Array of integers from 0 to 23
    var hours = [Int](0..<24)
    //Configurable minute number: Array of integers from 0 to 59
    var minutes = [Int](0..<60)
    //Configurable number in seconds: Array of integers from 0 to 59
    var seconds = [Int](0..<60)

struct PickerView: View {
    var body: some View {
    }
}

4. Place the Picker in the PickerView

Inside the PickerView, use HStack to arrange the following components horizontally in order from the left.

--Time Picker --Hourly text --Minute Picker --Minute text --Second Picker --Text in seconds

For the time Picker argument selection, specify the property hourSelection of the instance timeManager created earlier. This will cause the value selected by Picker to be assigned to the property of the same name in the TimeManager class.

In the {} of the Picker, make ForEach display a number in the Picker for each of 0 to 23, and specify the value (including the data type) to be acquired when the time is actually selected in the Picker by the tag modifier. .. The value to be acquired is an Int type, and the property hourSelection of the reflection destination TimeManager is also matched with an Int type.

Once you have an hour picker, specify that the Text component should display the unit "hour".

In the same way, create Picker and Text for minutes and seconds.

PickerView.swift


struct PickerView: View {
    //(Description of property part omitted)
    
    var body: some View {
            //Picker of hours, minutes, seconds and text showing each unit side by side in HStack
            HStack {
                //Hourly Picker
                Picker(selection: self.$timeManager.hourSelection, label: Text("hour")) {
                    ForEach(0 ..< self.hours.count) { index in
                        Text("\(self.hours[index])")
                            .tag(index)
                    }
                }
                //Specify a wheel style that rotates up and down
                .pickerStyle(WheelPickerStyle())
                //Picker width screen size x 0.1, height screen size x 0.Specified by 4
                .frame(width: self.screenWidth * 0.1, height: self.screenWidth * 0.4)
                //Clip with the upper frame and hide the part that extends beyond the frame
                .clipped()
                //Text representing the time unit
                Text("hour")
                    .font(.headline)
                
                //Minute Picker
                Picker(selection: self.$timeManager.minSelection, label: Text("minute")) {
                    ForEach(0 ..< self.minutes.count) { index in
                        Text("\(self.minutes[index])")
                            .tag(index)
                    }
                }
                .pickerStyle(WheelPickerStyle())
                .frame(width: self.screenWidth * 0.1, height: self.screenWidth * 0.4)
                .clipped()
                //Text representing minutes
                Text("min")
                    .font(.headline)
                
                //Picker in seconds
                Picker(selection: self.$timeManager.secSelection, label: Text("second")) {
                    ForEach(0 ..< self.seconds.count) { index in
                        Text("\(self.seconds[index])")
                            .tag(index)
                    }
                }
                .pickerStyle(WheelPickerStyle())
                .frame(width:self.screenWidth * 0.1, height: self.screenWidth * 0.4)
                .clipped()
                //Text in seconds
                Text("sec")
                    .font(.headline)
            }
    }
}

5. Create a method to reflect the time set in TimeManager

Create a method called setTimer in the TimeManager class. Since you can get the values of the TimeManager hour, minute, and second properties from PickerView, convert those values to seconds and add them up, and reflect them in the remaining time and maximum time properties. At the same time, the time display format should be conditional branching according to the total time.

TimeManager.swift


class TimeManager: ObservableObject {
    //Set with Picker"time"Variable to store
    @Published var hourSelection: Int = 0
    //Set with Picker"Minutes"Variable to store
    @Published var minSelection: Int = 0
    //Set with Picker"Seconds"Variable to store
    @Published var secSelection: Int = 0
    //Countdown time remaining
    @Published var duration: Double = 0
    //Maximum time before the countdown starts
    @Published var maxValue: Double = 0
    //Time display format that changes depending on the set time of 1 hour or more, less than 1 hour, 1 minute or more, less than 1 minute, 1 second or more
    @Published var displayedTimeFormat: TimeFormat = .min
    
    //Calculate the remaining countdown time and the maximum time before the countdown starts from the value obtained by Picker,
    //The time display format is also specified by the value.
    func setTimer() {
        //Calculate the remaining time by converting all the values of hours, minutes, and seconds obtained from Picker into seconds and totaling them.
        duration = Double(hourSelection * 3600 + minSelection * 60 + secSelection)
        //When you set the time with Picker=Remaining time because it is before the countdown starts=Maximum time
        maxValue = duration
        
        //Specify the time display format from the remaining time (maximum time)
        //00 format for less than 60 seconds, 00 for 60 seconds or more and less than 3600 seconds:00 format, 00 for 3600 seconds or more:00:00 format
        if duration < 60 {
            displayedTimeFormat = .sec
        } else if duration < 3600 {
            displayedTimeFormat = .min
        } else {
            displayedTimeFormat = .hr
        }
    }
}

In addition, add a button to PickerView so that the above setTimer method can be activated at the timing set by PickerView. This is because the formula "maximum time = remaining time" is included, so it is ideal before the countdown, that is, immediately after the time setting operation.

For the button icon, use "checkmark.circle.fill" from Apple's genuine SF Symbols.

The .offset modifier is placed slightly downward from the default center placement, and adjusted so that it does not overlap with PickerView.

With the .opacity modifier, if the hours, minutes, and seconds are all set to 0, the transparency is set to 10% (x 0.1), and it is visually expressed that you can not tap now.

When entering the argument of the modifier, instead of using if and else, write "△△? □□: ○○" to mean "If △△, then □□, otherwise ○○". It is convenient to be given an argument to conditional branch to. Also, "&&" means "katsu" and is used to express cases where multiple conditions are met.

.onTapGesture modifier{}If the value of the hour, minute, or second property is not 0, the setTimer method will be triggered when tapped. "" In the if statement||"" Means "or".

PickerView.swift


struct PickerView: View {
    //Create an instance of TimeManager
    @EnvironmentObject var timeManager: TimeManager
    //Device screen width
    let screenWidth = UIScreen.main.bounds.width
    //Device screen height
    let screenHeight = UIScreen.main.bounds.height
    //Settable time unit number
    var hours = [Int](0..<24)
    //Configurable minute number
    var minutes = [Int](0..<60)
    //Configurable number in seconds
    var seconds = [Int](0..<60)
    
    var body: some View {
        //Arrange buttons so that they overlap with Picker in layers in ZStack
        ZStack{
            //Picker of hours, minutes, seconds and text showing each unit side by side in HStack
            HStack {
                //(Picker part omitted)
            }
            
            //Check mark icon to confirm the setting by tapping
            Image(systemName: "checkmark.circle.fill")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 35, height: 35)
                .offset(y: self.screenWidth * 0.32)
                .opacity(self.timeManager.hourSelection == 0 && self.timeManager.minSelection == 0 && self.timeManager.secSelection == 0 ? 0.1 : 1)
                .onTapGesture {
                    if self.timeManager.hourSelection != 0 || self.timeManager.minSelection != 0 || self.timeManager.secSelection != 0 {
                        self.timeManager.setTimer()
                    }
            }
        }
    }
}

6. Create a method to display the remaining time during the countdown in TimeManager

Make it a method named displayTimer. Returns a String type return value.

The calculation of the remaining time is arithmetic, but it is as follows. By the way,% is an operator that can calculate the remainder of division.

--Total remaining time (seconds) / 3600 (seconds) = remaining time (hours) --Total remaining time (seconds)% 3600 (seconds) / 60 (seconds) = remaining time (minutes) --Total remaining time (seconds) / 3600 (seconds)% 60 (seconds) = remaining time (seconds)

The three numerical values obtained by the above calculation are displayed side by side on the screen as character string type data.

String(format: "%02d:%02d:%02d", hr, min, sec)

This code is written as String (format: “specify number of digits”, value). In particular,% 02d in "" means that the total number of digits is 2 and if it is less than 2 digits, it is filled with 0 from the largest digit. And the values listed after% 02d are entered in order from the left, separated by commas.

For example, the following is displayed on the screen.

--If the remaining time is 4000 seconds 01:06:40 --If the remaining time is 350 seconds 05:50 ――If the remaining time is 7 seconds, 07

TimeManager.swift


class TimeManager: ObservableObject {
    //(abridgement)
    
    //Method for displaying the remaining time during the countdown
    func displayTimer() -> String {
        //Remaining time (in hours)=Total time remaining (seconds)/3600 seconds
        let hr = Int(duration) / 3600
        //Remaining time (in minutes)=Total time remaining (seconds)/Residual divided by 3600 seconds/60 seconds
        let min = Int(duration) % 3600 / 60
        //Remaining time (in seconds)=Total time remaining (seconds)/Residual divided by 3600 seconds/Residual divided by 60 seconds
        let sec = Int(duration) % 3600 % 60
        
        //Conditionally branch the time display format according to the result of the setTimer method, and reflect the above three constants in combination.
        switch displayedTimeFormat {
        case .hr:
            return String(format: "%02d:%02d:%02d", hr, min, sec)
        case .min:
            return String(format: "%02d:%02d", min, sec)
        case .sec:
            return String(format: "%02d", sec)
        }
    }
}

The PickerView created so far should look like the image below. スクリーンショット 2020-10-28 1.38.43.png

Next time, we will recalculate the remaining time from the time set in Picker and display the timer on the screen.

Recommended Posts

iOS app development: Timer app (1. Timer time setting)
iOS app development: Timer app (2. Timer display)
iOS app development: Timer app (summary)
iOS app development: Timer app (6. Creation of setting screen)
iOS app development: Timer app (4. Countdown implementation)
iOS app development: Timer app (10. Create animation)
iOS app development: Timer app (8. Implementation of progress bar)
iOS app development: Timer app (3. Start / Stop button, Reset button)
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 (9. Customize the color of the progress bar)
Complete self-study IOS app development diary
Error Treatment Techniques Hit in iOS App Development ~ Xcode Edition ~
iOS engineer starts Android development
Android app personal development kickoff
ROS app development on Android
ATDD development on iOS (basic)
NoCode Glide's first app development