Timer/timer publisher and subscriber, use of Subscribar/text box subscription mode

1. Timer operation

1.1 Implementation

/// Timer publisher and subscriber
struct TimerBootcamp: View {
    
    // Timer publisher timer1/timer3/timer5 = 1.0, timer2 = 2.0, timer4 = 0.5
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    // ---1---
    // Computed property current time
    @State var currentDate = Date()
    var dateFormatter: DateFormatter{
        let formatter = DateFormatter()
        //formatter.dateStyle = .medium
        formatter.timeStyle = .medium
        return formatter
    }
    
    // ---2---
    // CountDown countdown
    @State var countDown: Int = 10
    @State var finishedText: String? = nil
    
    // ---3---
    // Countdown to date
    @State var timeRemaining: String = ""
    // Future time one day ahead
    let futureDate:Date = Calendar.current.date(byAdding: .hour, value: 1, to: Date())  Date()
    //update time
    func updateTimeRemaRemaining(){
        //.hour,
        let remaining = Calendar.current.dateComponents([.minute, .second], from: Date(), to: futureDate)
        //let hour = remaining.hour  0
        let minute = remaining.minute  0
        let second = remaining.second  0
        //\(hour) :
        timeRemaining = " \(minute) minutes, \(second) seconds"
    }
    
    // ---4---
    // Animation counter Animation counter
    @State var countCircle: Int = 0
    
    // ---5---
    // animation page
    @State var count: Int = 1
    
    //Background View
    var backgroundView: some View{
        // gradient
        RadialGradient(
            gradient: Gradient(colors: [Color(#colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1)), Color(#colorLiteral(red: 0.09019608051, green: 0, blue: 0.3019607961, alpha : 1 ))]),
            center: .center,
            startRadius: 5,
            endRadius: 500)
        .ignoresSafeArea()
    }
    
    var body: some View{
        //timer1
        //timer2
        //timer3
        //timer4
        timer5
    }
    
    // Method five
    var timer5: some View{
        ZStack {
            // background view
            backgroundView
            
            TabView(selection: $count) {
                Rectangle()
                    .foregroundColor(.red)
                    .tag(1)
                Rectangle()
                    .foregroundColor(.blue)
                    .tag(2)
                Rectangle()
                    .foregroundColor(.green)
                    .tag(3)
                Rectangle()
                    .foregroundColor(.orange)
                    .tag(4)
                Rectangle()
                    .foregroundColor(.gray)
                    .tag(5)
            }
            .frame(height: 200)
            .tabViewStyle(.page)
        }
        // Listening timer
        .onReceive(timer) { _ in
            // add animation
            withAnimation(.default){
                count = count == 5 ? 1 : count + 1
            }
        }
    }
    
    // Method 4
    var timer4: some View{
        ZStack {
            // background view
            backgroundView
            
            HStack(spacing: 15) {
                Circle()
                    .offset(y: countCircle == 1 ? -20 : 0)
                Circle()
                    .offset(y: countCircle == 2 ? -20 : 0)
                Circle()
                    .offset(y: countCircle == 3 ? -20 : 0)
            }
            .frame(width: 150)
            .foregroundColor(.white)
        }
        // Listening timer
        .onReceive(timer) { _ in
            // add animation
            withAnimation(.easeInOut(duration: 0.5)){
                countCircle = countCircle == 3 ? 0 : countCircle + 1
            }
        }
    }
    
    // Method three
    var timer3: some View{
        ZStack {
            // background view
            backgroundView
            
            // display text
            Text(timeRemaining)
                .font(.system(size: 100, weight: .semibold, design: .rounded))
                .foregroundColor(.white)
                .lineLimit(1) // keep 1 line
                .minimumScaleFactor(0.1) //The minimum scale factor is 0.1
        }
        // Listening timer
        .onReceive(timer) { _ in
            updateTimeRemaRemaining()
        }
    }
    
    // Method 2
    var timer2: some View{
        ZStack {
            // background view
            backgroundView
            
            // display text
            Text(finishedText  "\(countDown)")
                .font(.system(size: 100, weight: .semibold, design: .rounded))
                .foregroundColor(.white)
                .lineLimit(1) // keep 1 line
                .minimumScaleFactor(0.1) //The minimum scale factor is 0.1
        }
        // Listening timer
        .onReceive(timer) { _ in
            if countDown <= 1 {
                finishedText = "Wow!"
            }else{
                countDown -= 1
            }
        }
    }
    
    // method one
    var timer1: some View{
        ZStack {
            //Background View
            backgroundView
            
            // display text
            Text(dateFormatter.string(from: currentDate))
                .font(.system(size: 100, weight: .semibold, design: .rounded))
                .foregroundColor(.white)
                .lineLimit(1) // keep 1 line
                .minimumScaleFactor(0.1) //The minimum scale factor is 0.1
        }
        // Listening timer
        .onReceive(timer) { output in
            currentDate = output
        }
    }
}

1.2 Rendering:

2. Subscribar input text box monitors text subscriber mode

2.1 Implementation

import Combine

/// ViewModel
class SubscribarViewModel: ObservableObject{
    @Published var count: Int = 0
    
    // var timer: AnyCancellable?
    var canncellables = Set<AnyCancellable>()
    
    // Publisher: You can subscribe to the value published when the publisher changes
    @Published var textFieldText: String = ""
    @Published var textIsValid: Bool = false
    @Published var showButton: Bool = false
    
    init() {
        setUpTimer()
        addTextFieldSubscriber()
        addButtonSubscriber()
    }
    
    //Set timer
    func setUpTimer(){
        //Send regularly timer =
        Timer
            .publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] _ in
                guard let self = self else { return }
                self.count + = 1
                //if self.count >= 10 {
                //self?.count = 0
                //self.timer?.cancel()
                //for item in canncellables{
                // item.cancel()
                //}
                //}
            }
            .store(in: & amp;canncellables)
    }
    
    // Enter text subscriber
    func addTextFieldSubscriber(){
        $textFieldText
        // debounce debounce. When the input stops, execute the following code every 0.5 seconds.
            .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
            .map { text in
                if text.count > 3 {
                    return true
                }
                return false
            }
        // It is not recommended to use .assign because it uses a strong reference and cannot be modified. It is recommended to use .sink
        //.assign(to: \.textIsValid, on: self)
            .sink(receiveValue: {[weak self] isValid in
                guard let self = self else { return }
                self.textIsValid = isValid
            })
        // Cancel at any time
            .store(in: & amp;canncellables)
    }
    
    //Add button subscriber
    func addButtonSubscriber(){
        $textIsValid
        // Combine latest values
            .combineLatest($count)
            .sink { [weak self] isValid, count in
                guard let self = self else { return }
                if isValid & amp; & amp; count >= 5 {
                    self.showButton = true
                }else{
                    self.showButton = false
                }
            }
            .store(in: & amp;canncellables)
    }
}

// Subscriber mode
struct SubscriberBootcamp: View {
    @StateObject var viewModel = SubscribarViewModel()
    
    var body: some View {
        VStack {
            Text("\(viewModel.count)")
                .font(.largeTitle)
            
            TextField("Type something here...", text: $viewModel.textFieldText)
                .frame(height: 55)
                .padding(.horizontal)
                .font(.headline)
                .background(Color.gray.opacity(0.3))
                .cornerRadius(10)
                .overlay(alignment: .trailing) {
                    ZStack() {
                        Image(systemName: "xmark")
                            .foregroundColor(.red)
                            .opacity(viewModel.textFieldText.count < 1 ? 0.0:
                                        viewModel.textIsValid ? 0.0 : 1.0)
                        Image(systemName: "checkmark")
                            .foregroundColor(.accentColor)
                            .opacity(viewModel.textIsValid ? 1.0 : 0.0)
                    }
                    .font(.title)
                    .padding(.trailing)
                }
            
            Button {
            } label: {
                Text("Submit")
                    .font(.headline)
                    .foregroundColor(.white)
                    .frame(height: 55)
                    .frame(maxWidth: .infinity)
                    .background(Color.accentColor)
                    .cornerRadius(10)
                    .opacity(viewModel.showButton ? 1.0 : 0.5)
            }
            .disabled(!viewModel.showButton)
        }
        .padding()
    }
}

2.2 Rendering: