如何将值从另一个 swift 文件传递到另一个文件?

问题描述 投票:0回答:1

我想创建一个带有计时器的音频播放器。动作顺序如下:

  • 用户使用选择器(TimerViews)选择计时器持续时间。

  • 当用户点击计时器上的“开始”时,页面将重定向到音频播放器页面(AudioPlayerViews)。

  • 在AudioPlayerViews页面上,计时器和音频不会立即启动。相反,计时器将显示用户在 TimerViews 页面上输入的值。

  • 当用户单击 AudioPlayerViews 上的“播放”时,倒计时器和歌曲将开始。

  • 当倒计时完成或到达00:00:00时,歌曲将自动停止。

我坚持将计时器值从 TimerViews 页面传递到 AudioPlayerViews 页面。

有人可以帮助我吗?这是到目前为止的代码。

//
//  ContentView.swift
//

import SwiftUI

struct ContentView: View {
    @State private var isTimerVisible = false
    @StateObject private var timer = TimerViewModel()
    
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: TimerViews()) {
                    Text("Set Timer")
                }
                
                if isTimerVisible {
                    AudioPlayerViews()
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
//
//  TimerViews.swift
//

import SwiftUI
import Foundation

struct TimerViews: View {
    @StateObject private var timerViewModel = TimerViewModel()
    @State private var audioPlayerModel = AudioPlayerModel()
    
    var body: some View {
        
        NavigationView {
            ZStack {
                //MARK: Background
                Image("BgColor")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .edgesIgnoringSafeArea(.all)
                
                VStack {
                    //MARK: Timer Picker
                    if timerViewModel.isPickerVisible {
                        HStack {
                            Picker("Hour", selection: $timerViewModel.selectedHours) {
                                ForEach(0..<24) {
                                    Text("\(self.timerViewModel.hours[$0]) hour")
                                }
                            }
                            
                            Picker("Min", selection: $timerViewModel.selectedMins) {
                                ForEach(0..<60) {
                                    Text("\(self.timerViewModel.minutes[$0]) min")
                                        .foregroundColor(.white)
                                }
                            }
                            
                            Picker("Sec", selection: $timerViewModel.selectedSecs) {
                                ForEach(0..<60) {
                                    Text("\(self.timerViewModel.seconds[$0]) sec")
                                        .foregroundColor(.white)
                                }
                            }
                        }
                        .accentColor(.white)
                    }
                    
                    HStack {
                        //MARK: Timer Preview
                        Text(String(format: "%02d:%02d:%02d", timerViewModel.calculate() / 3600, (timerViewModel.calculate() / 60) % 60, timerViewModel.calculate() % 60))
                            .font(.system(size: 50, weight: .medium, design: .rounded))
                            .padding()
                    }
                    
                    HStack {
                        NavigationLink(destination: AudioPlayerViews()) {
                            Text("Start")
                        }
                    
                        VStack {
                            //MARK: Restart Timer Button
                            Button {
                                timerViewModel.restart()
                                timerViewModel.audioPlayer.setupAudio()
                            } label: {
                                Text("Restart")
                            }
                        }
                        
                    }
                }
                .foregroundColor(.white)
            }
            .onDisappear {
                // Invalidate the timer when the view disappears
                timerViewModel.timer?.invalidate()
            }
        }
    }
}

struct TimerViews_Previews: PreviewProvider {
    static var previews: some View {
        TimerViews()
    }
}
//
//  TimerViewModel.swift
//

import Foundation

class TimerViewModel: ObservableObject {
    @Published var hours = Array(0...23)
    @Published var minutes = Array(0...59)
    @Published var seconds = Array(0...59)
    @Published var selectedHours = 0
    @Published var selectedMins = 0
    @Published var selectedSecs = 5
    @Published var isRunning = false
    @Published var isPickerVisible = true
    @Published var timer: Timer?
    @Published var audioPlayer = AudioPlayerModel()

    func calculate() -> Int {
        let totalSecs = selectedSecs + selectedMins * 60 + selectedHours * 3600
        return totalSecs
    }

    func restart() {
        timer?.invalidate()
        isRunning = false
        isPickerVisible = true
        selectedHours = 0
        selectedMins = 0
        selectedSecs = 0
        audioPlayer.pauseAudio()
    }

    func start() {
        isRunning = true
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
            guard let self = self else { return }
            self.selectedSecs -= 1

            if self.calculate() <= 0 {
                self.restart()
            }
        }
    }

    func pause() {
        isRunning = false
        timer?.invalidate()
    }
}
//
//  AudioPlayerViews.swift
//

import SwiftUI
import AVFoundation

struct AudioPlayerViews: View {
    @ObservedObject var audioManager = AudioPlayerModel()
    @ObservedObject var timerModel = TimerViewModel()
    @State private var showingCredits = false
    
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                Image(audioManager.currentSong?.image ?? "1")
                    .resizable()
                    .edgesIgnoringSafeArea(.all)
                    .scaledToFill()
                    .frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
                
                
                VStack {
                    //MARK: Credit Info
                    HStack {
                        Button {
                            showingCredits = true
                        } label: {
                            Image(systemName: "info.circle")
                                .foregroundColor(.white)
                                .frame(maxWidth: .infinity, alignment: .trailing)
                                .padding(.trailing)
                        }
                        .sheet(isPresented: $showingCredits) {
                            Text(audioManager.currentSong?.copyrightInfo ?? "No Info")
                                .presentationDetents([.fraction(0.12)])
                                .presentationDragIndicator(.visible)
                                .foregroundColor(.black)
                                .font(.caption2)
                                .padding(.top)
                                .padding(.leading)
                                .padding(.trailing)
                        }
                    }
                    
                    Spacer()
                    
                    //MARK: Timer Countdown
                    HStack {
                        Text(String(format: "%02d:%02d:%02d", timerModel.selectedHours / 3600, timerModel.selectedMins / 60, timerModel.selectedSecs % 60))
                            .font(.system(size: 25, weight: .medium, design: .rounded))
                            .padding()
                    }
                    
                    //MARK: Audio Button
                    HStack (spacing: 20) {
                        Button {
                            audioManager.playPrevSong()
                        } label: {
                            Image(systemName: "backward.circle.fill")
                                .resizable()
                                .frame(width: 40, height: 40)
                                .scaledToFit()
                        }
                        
                        Button {
                            if audioManager.isPlaying {
                                audioManager.audioPlayer?.pause()
                                timerModel.pause()
                            } else {
                                audioManager.playAudio()
                                timerModel.start()
                            }
                            audioManager.isPlaying.toggle()
                        } label: {
                            Image(systemName: audioManager.isPlaying ? "pause.circle.fill" : "play.circle.fill")
                                .resizable()
                                .frame(width: 60, height: 60)
                                .scaledToFit()
                        }
                        .tint(.black)
                        
                        Button {
                            audioManager.playNextSong()
                        } label: {
                            Image(systemName: "forward.circle.fill")
                                .resizable()
                                .frame(width: 40, height: 40)
                                .scaledToFit()
                        }
                    }
                    
                    //MARK: Song Title
                    VStack {
                        Text(audioManager.currentSong?.songName ?? "No Name")
                            .bold()
                            .font(.title2)
                            .frame(maxWidth: .infinity, alignment: .center)
                        Text(audioManager.currentSong?.composer ?? "No Composer")
                            .font(.caption)
                            .frame(maxWidth: .infinity, alignment: .center)
                    }
                    .padding(.bottom)
                    
                }
                .foregroundColor(.white)
            }
        }
    }
}

struct AudioPlayerViews_Previews: PreviewProvider {
    static var previews: some View {
        AudioPlayerViews()
    }
}
//
//  AudioPlayerModel.swift
//

import AVFoundation

struct Song: Identifiable {
    let id = UUID()
    let songName: String
    let composer: String
    let audioFileName: String
    let image: String
    let copyrightInfo: String
}

class AudioPlayerModel: NSObject, ObservableObject, AVAudioPlayerDelegate {
    @Published private var timerView = ContentView()
    private var currentIndex = 0
    @Published var audioPlayer : AVAudioPlayer?
    @Published var isPlaying = false
    @Published var currentTime: TimeInterval = 0
    @Published var songs: [Song] = [
        Song(songName: "Signal to Noise", composer: "Scott Buckley", audioFileName: "Signal to Noise.mp3", image: "1", copyrightInfo: "www.scottbuckley.com.au Music promoted by https://www.chosic.com/free-music/all/ Creative Commons CC BY 4.0 https://creativecommons.org/licenses/by/4.0/ Image: https://www.pxfuel.com/"),
        Song(songName: "Arnor", composer: "Alex Productions", audioFileName: "Arnor.mp3", image: "2", copyrightInfo: "https://onsound.eu/ Music promoted by https://www.chosic.com/free-music/all/ Creative Commons CC BY 3.0 https://creativecommons.org/licenses/by/3.0/ Image: https://www.pxfuel.com/"),
    ]
    
    @Published var currentSong: Song?
    
    override init() {
        super .init()
        currentSong = self.songs.first
        setupAudio()
    }
    
    func setupAudio() {
        guard let currentSong = currentSong else { return }
        if let audioPlayer = audioPlayer {
            audioPlayer.stop()
            audioPlayer.delegate = nil
        }
        if let audioFileURL = Bundle.main.url(forResource: currentSong.audioFileName, withExtension: nil) {
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: audioFileURL)
                audioPlayer?.delegate = self
                audioPlayer?.prepareToPlay()
                isPlaying = false
            } catch {
                print("Error: \(error)")
            }
        }
    }
    
    func autoRepeat() {
        guard let currentSong = currentSong else { return }
        if let audioPlayer = audioPlayer {
            audioPlayer.stop()
            audioPlayer.delegate = nil
        }
        if let audioFileURL = Bundle.main.url(forResource: currentSong.audioFileName, withExtension: nil) {
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: audioFileURL)
                audioPlayer?.delegate = self
                audioPlayer?.play()
            } catch {
                print("Error: \(error)")
            }
        }
    }
    
    func playNextSong() {
        if currentIndex == songs.count - 1 {
            currentIndex = 0 // Kembali ke lagu pertama jika mencapai akhir
        } else {
            currentIndex += 1
        }
        currentSong = songs[currentIndex]
        autoRepeat()
    }
    
    func stopAudio() {
        audioPlayer?.stop()
        isPlaying = false
    }
    
    func playPrevSong() {
        currentIndex -= 1
        if currentIndex < 0 {
            currentIndex = songs.count - 1
        }
        currentSong = songs[currentIndex]
        autoRepeat()
    }
    
    func playAudio() {
        audioPlayer?.play()
    }
    
    func pauseAudio() {
        audioPlayer?.pause()
    }
    
    func formatTime(_ timeInterval: TimeInterval) -> String {
        let minutes = Int(timeInterval / 60)
        let seconds = Int(timeInterval.truncatingRemainder(dividingBy: 60))
        return String(format: "%02d: %02d", minutes, seconds)
    }
    
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        isPlaying = true
        currentTime = 0
        playNextSong()
    }
}
swiftui timer parameter-passing
1个回答
0
投票

为了引用和传递已在 viewModel 中发布的数据,您不应在每个视图中对其进行初始化。相反,将 viewModel 数据初始化为 ContentView 中的

@StateObject
,然后使用
@ObservedObject var timerModel: TimerViewModel
传递它。另外,当连续使用
NavigationView
时,请记住 NavigationView 本身携带数据。因此,您应该删除子视图中声明的
NavigationView
,以确保单个数据层次结构有效运行。高亮需要修改部分的代码如下:

内容视图

struct ContentView: View {
    @State private var isTimerVisible = false
    @StateObject private var timer = TimerViewModel()
    
    var body: some View {
// As NavigationView has been deprecated, it's recommended to use NavigationStack instead
        NavigationStack {
            VStack {
                // add parameter
                NavigationLink(destination: TimerViews(timerViewModel: timer)) {
                    Text("Set Timer")
                }
                
                if isTimerVisible {
                    // add parameter
                    AudioPlayerViews(timerModel: timer)
                }
            }
        }
    }
}

计时器视图

import SwiftUI
import Foundation

struct TimerViews: View {
    // Receive it as a parameter from the parent view instead of initializing
    @ObservedObject var timerViewModel: TimerViewModel
    @State private var audioPlayerModel = AudioPlayerModel()
    
    var body: some View {
            // remove NavigationView
            ZStack {
                Color.purple
                //MARK: Background
                Image("BgColor")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .edgesIgnoringSafeArea(.all)
                
                VStack {
                    //MARK: Timer Picker
                    if timerViewModel.isPickerVisible {
                        HStack {
                            Picker("Hour", selection: $timerViewModel.selectedHours) {
                                ForEach(0..<24) {
                                    Text("\(self.timerViewModel.hours[$0]) hour")
                                }
                            }
                            
                            Picker("Min", selection: $timerViewModel.selectedMins) {
                                ForEach(0..<60) {
                                    Text("\(self.timerViewModel.minutes[$0]) min")
                                        .foregroundColor(.white)
                                }
                            }
                            
                            Picker("Sec", selection: $timerViewModel.selectedSecs) {
                                ForEach(0..<60) {
                                    Text("\(self.timerViewModel.seconds[$0]) sec")
                                        .foregroundColor(.white)
                                }
                            }
                        }
                        .accentColor(.white)
                    }
                    
                    HStack {
                        //MARK: Timer Preview
                        Text(String(format: "%02d:%02d:%02d", timerViewModel.calculate() / 3600, (timerViewModel.calculate() / 60) % 60, timerViewModel.calculate() % 60))
                            .font(.system(size: 50, weight: .medium, design: .rounded))
                            .padding()
                    }
                    
                    HStack {
                        // add parameter
                        NavigationLink(destination: AudioPlayerViews(timerModel: timerViewModel)) {
                            Text("Start")
                        }
                    
                        VStack {
                            //MARK: Restart Timer Button
                            Button {
                                timerViewModel.restart()
                                timerViewModel.audioPlayer.setupAudio()
                            } label: {
                                Text("Restart")
                            }
                        }
                        
                    }
                }
                .foregroundColor(.white)
            }
            .onDisappear {
                // Invalidate the timer when the view disappears
                timerViewModel.timer?.invalidate()
            }
        
    }
}

音频播放器视图

import SwiftUI
import AVFoundation

struct AudioPlayerViews: View {
    @ObservedObject var audioManager = AudioPlayerModel()
    // same change as TimerView
    @ObservedObject var timerModel: TimerViewModel
    @State private var showingCredits = false
    
    var body: some View {
       // AudioPlayerViews code ...
    }
}

此外,您似乎通过分别传递小时、分钟和秒来混合方法,而计时器仅转换秒。所以,如果你按照我告诉你的方式修复它,它可能只会通过并在“秒”内工作。您需要检查单位以确定如何使用计时器。

    func start() {
        isRunning = true
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
            guard let self = self else { return }
            // your only using selectedSecs here
            self.selectedSecs -= 1

            if self.calculate() <= 0 {
                self.restart()
            }
        }
    }

很抱歉我无法解决您的所有问题,但希望我的解决方案对您有所帮助。祝你好运。

© www.soinside.com 2019 - 2024. All rights reserved.