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.
You can see the sample code from the URL of the Git repository below. https://github.com/msnsk/Qiita_Timer.git
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 {
}
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
}
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 {
}
}
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)
}
}
}
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()
}
}
}
}
}
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.
Next time, we will recalculate the remaining time from the time set in Picker and display the timer on the screen.
Recommended Posts