将滑块值存储到全局变量中

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

我的 Swift 程序中有几个相互嵌套的视图。在最低级别的视图中,我有一个滑块。我想将它的值从这个视图传递到全局变量存储在结构中的主 Swift 文件。该值将用于控制使用 AudioKit 播放的音频的音量。

滑块代码(此处不包括完整视图,因为其余部分无关紧要):

struct MasterFaderView: View {
    var body: some View {
        Slider(
            value: master.Value,
            in: 0...127,
            step: 1.0)
    }
}

正在设置的顶级全局变量:

struct channel {
    var Value: Double = 0.0             // Default fader value
    ... // Other variables being stored
}

var master = channel()

@main
struct MEngProjectApp: App {
    
    var body: some Scene {
        WindowGroup {
            FreePlayView()
        }
    }
}

FreePlayView() 是主视图,其中包含 audioKit 内容并将控制音量:

class MixerClass: ObservableObject {
    let engine = AudioEngine()
    var fullTrack = AudioPlayer()
    
    init() {
        engine.output = fullTrack
        loadFiles()
        try? engine.start()
    }
    
    func loadFiles() {
        do {
            if let fileURL = Bundle.main.url(forResource: "DQ_FullTrack", withExtension: "wav") {
                try fullTrack.load(url: fileURL)
            } else {
                Log("Could not find file")
            }
        } catch {
            Log("Could not load full track")
        }
    }
}

struct FreePlayView: View {
    @StateObject var conductor = MixerClass()
    @State var trackPlaying: Bool = false
    
    var body: some View {
        Button(action: {
        if trackPlaying == false {
            conductor.fullTrack.start()
            conductor.fullTrack.volume = master.Value
            trackPlaying = true
        } else {
            conductor.fullTrack.stop()
            trackPlaying = false
        }
   }) {
      Text("Start Audio")
   }

我在 MasterFaderView 中得到的错误代码是

Cannot convert value of type 'Double' to expected argument type 'Binding<Double>'

有人知道如何解决这个问题吗?

提前致谢。

swift swiftui audiokit
2个回答
0
投票

要在你的应用程序的不同地方使用一个变量,你可以使用一个

ObservableObject
类, 带有
@Published var value: Double = 0.0
,如
class ChannelModel: ObservableObject
所示。 每当
value
改变时,UI 将刷新。

在您的应用程序中声明此

ChannelModel
,并且 使用
.environmentObject(channelModel)
和传递它
@EnvironmentObject var channel: ChannelModel
如图所示
FreePlayView
MasterFaderView
.

这里是这种方法的一些示例代码:

@main
struct MEngProjectApp: App {
    @StateObject var channelModel = ChannelModel() // <-- here

    var body: some Scene {
        WindowGroup {
            FreePlayView()
                .environmentObject(channelModel)  // <-- here
        }
    }
}

class ChannelModel: ObservableObject {  // <-- here
    @Published var value: Double = 0.0  // <-- here
    // ... Other variables being stored
}

struct FreePlayView: View {
    @EnvironmentObject var channelModel: ChannelModel  // <-- here
    
    @StateObject var conductor = MixerClass()
    @State var trackPlaying: Bool = false
    
    var body: some View {
        Button(action: {
            if trackPlaying == false {
                conductor.fullTrack.start()
                conductor.fullTrack.volume = channelModel.value  // <-- here
                trackPlaying = true
            } else {
                conductor.fullTrack.stop()
                trackPlaying = false
            }
        }) {
            Text("Start Audio")
        }
    }
}

struct MasterFaderView: View {
    @EnvironmentObject var channelModel: ChannelModel  // <-- here
    
    var body: some View {
        Slider(value: $channelModel.value, in: 0...127, step: 1.0)  // <-- here
    }
}

0
投票

@Wpitchy,

在您的代码片段中,我没有看到您在哪里通过滑块的值更新

fullTrack.volume

我还会将

@Published var fullTrackVolume
变量保留在
Conductor
中,而不是在它自己的
ChannelMode
类中,因为它会影响在那里实例化的
fullTrack
AudioPlayer。

这是我要改变的:

MEngProjectApp.swift

import SwiftUI

@main
struct MEngProjectApp: App {

    // If you want the MixerClass to be available in multiple views (not just FreePlayView), you will want to instantiate it as an EnvironmentObject in the App level.
    @StateObject var conductor = MixerClass()

    var body: some Scene {
        WindowGroup {
            FreePlayView()
                .environmentObject(conductor) // <- Passing this object into this parent view will allow the MixerClass to be accessible from the FreePlayView and any of its child views.
        }
    }
} 

MixerClass.swift

import AudioKit
import AVFoundation
import Foundation

final class MixerClass: ObservableObject {

    let engine = AudioEngine()

    @Published var fullTrack = AudioPlayer() // <- Published vars broadcast any updates that occur, such as volume level changes.
    @Published var isTrackPlaying: Bool = false

    init() {

        // Importing AVFoundation is required in order to access the AVAudioSession settings.
        do {
            Settings.bufferLength = .medium
            try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(Settings.bufferLength.duration)
            try AVAudioSession.sharedInstance().setCategory(.playAndRecord,
                                                            options: [.defaultToSpeaker,
                                                                      .mixWithOthers,
                                                                      .allowBluetoothA2DP])
            try AVAudioSession.sharedInstance().setActive(true)
        } catch let err {
            print(err)
        }

        loadFiles()
        fullTrack.isLooping = true // <- Set this to true, if you don't want the audio clip to stop playing.
        engine.output = fullTrack
        try? engine.start()
    }

    func loadFiles() {
        do {
            if let fileURL = Bundle.main.url(forResource: "DQ_FullTrack", withExtension: "wav") {
                try fullTrack.load(url: fileURL)
            } else {
                Log("Could not find file")
            }
        } catch {
            Log("Could not load full track")
        }
    }

    func convertVolumeToPercent() -> String {
        "\(Int(fullTrack.volume * 100))%" // <- Converts 0.5 to 50% and 1.0 to 100%, etc.
    }

    // MARK: User Intent Actions from any or multiple views.
    // This way, the business logic "decision-making" from triggered events will remain outside of any of the views. These actions can be repurposed, and not tied to any view code.

    func playStopToggle() {
        isTrackPlaying ? fullTrack.stop() : fullTrack.start()
        isTrackPlaying.toggle()
    }
}

FreePlayView.swift

import SwiftUI

struct FreePlayView: View {

    @EnvironmentObject var conductor: MixerClass

    var body: some View {
        VStack {
            MasterFaderView()
            playStopButton
        }
    }

    private var playStopButton: some View {
        Button(action: {
            conductor.playStopToggle()
        }) {
            Text("\(Image(systemName: conductor.isTrackPlaying ? "stop.fill" : "play.fill")) \(conductor.isTrackPlaying ? "Stop" : "Start") Audio")
        }
    }
}

struct FreePlayView_Previews: PreviewProvider {
    static var previews: some View {
        FreePlayView()
            .environmentObject(MixerClass()) // <- Pass in the MixerClass in order for the SwiftUI Preview to work without crashing.
    }
}

MasterFaderView.swift

import SwiftUI

struct MasterFaderView: View {

    @EnvironmentObject var conductor: MixerClass

    var body: some View {
        VStack {
            Text(conductor.convertVolumeToPercent())
                .font(.title).fontWeight(.thin)
            Slider(value: $conductor.fullTrack.volume, in: 0...1) // <- Volume in the AudioPlayer is 0.0-1.0, not 0-127.
            .padding()
        }
    }
}

struct MasterFaderView_Previews: PreviewProvider {
    static var previews: some View {
        MasterFaderView()
            .environmentObject(MixerClass()) // <- Pass in the MixerClass in order for the SwiftUI Preview to work without crashing.
    }
}

如果有帮助,请告诉我。

如果您想在更大的 Xcode 项目中看到它,请查看以下内容:

https://github.com/markjeschke/AKVolumeControl

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