在iOS上快速播放声音的最佳方法是什么?

waxmsbnn  于 2023-06-07  发布在  iOS
关注(0)|答案(1)|浏览(318)

我有一个用户界面,允许某人移动一个拨号盘和拨号盘‘卡扣‘到拨号盘上的每个‘标记‘上。
我想给这个添加声音,我已经做了一个非常短的“咔嗒”的声音,这是一秒的分数。
我不想限制用户旋转表盘的速度,但我希望在表盘转到每个标记时播放声音。
所以我需要一个快速响应的音频库来使用,但是我也知道我需要限制它播放的次数,以防他们旋转得太快,否则声音会变成一个持续的噪音,而不是明显的点击。
我看到评论说AVFoundation太慢了,AVAudioEngine会给予更好的性能,但我仍然不确定这是否是最好的方法,以及如何解决限制“重复声音”,使其不仅仅是一个可怕的噪音。
我意识到这是游戏程序员比非游戏iOS应用程序开发人员处理更多的事情,但我仍然坚持一种方法。

wyyhbhjk

wyyhbhjk1#

一种方法……
每当“当前刻度线”***发生变化***时,播放“点击”声。
这将略有不同,这取决于你是如何动画的“拨号”-但概念是相同的。让我们以滚动视图为例。
对于可滚动的内容,我们将使用一个视图,并每隔20点绘制一个垂直的“刻度线”,甚至在100点的位置上更高。我们还将在水平中心附近用一条垂直线覆盖一个视图--所以当一个刻度点到那条线时,我们想播放一个“点击”。我们将调整内容的大小,以便只能水平滚动。
它看起来像这样:

在稍微滚动之后:

当使用典型的滚动视图实现scrollViewDidScroll(...)时,快速滚动是非常容易的。如此之快,以至于.contentOffset.x可以在两次通话之间改变200+个点。
如果我们尝试在每20点变化时播放滴答声,我们可能会在基本相同的时间播放10次。
所以,我们可以创建一个类属性:

var prevTickMark: Int = 0

然后计算scrollViewDidScroll(...)中的 * 当前刻度线 *。如果值不同,则播放滴答声:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    var cx = Int(scrollView.contentOffset.x)
    
    // offset to the first tick-mark
    cx += Int(scrollView.contentInset.left)
    
    let curTick: Int = cx / 20
    if prevTickMark != curTick {
        // we just passed, or we are on, a new "tick"
        //  so play the tick sound
        AudioServicesPlayAlertSound(SystemSoundID(1057))
        prevTickMark = curTick
    }
}

如果我们非常非常快地滚动/拖动,我们不需要点击每个刻度标记...因为我们没有看到每一个刻度线都穿过中线。
当滚动减速时--或者当拖动缓慢时--我们会在每个刻度上得到一个点击。
这里有一些快速的示例代码来尝试...

TickView-每20点打一次勾

class TickView: UIView {
    
    lazy var tickLayer: CAShapeLayer = self.layer as! CAShapeLayer
    override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder:aDecoder)
        commonInit()
    }
    
    private func commonInit() {
        tickLayer.fillColor = nil
        tickLayer.strokeColor = UIColor.red.cgColor
        backgroundColor = .yellow
    }
    override func layoutSubviews() {
        super.layoutSubviews()

        let y: CGFloat = bounds.maxY * 0.75
        let shortTick: CGFloat = bounds.maxY * 0.25
        let tallTick: CGFloat = bounds.maxY * 0.5

        let bez = UIBezierPath()
        
        var pt: CGPoint = .init(x: bounds.minX, y: y)

        // horizontal line full width of view
        bez.move(to: pt)
        bez.addLine(to: .init(x: bounds.maxX, y: pt.y))

        // add vertical "tick" lines every 20-points
        //  with a taller line every 100-points
        bez.move(to: pt)

        while pt.x <= bounds.maxX {
            bez.move(to: pt)
            if Int(pt.x) % 100 == 0 {
                bez.addLine(to: .init(x: pt.x, y: pt.y - tallTick))
            } else {
                bez.addLine(to: .init(x: pt.x, y: pt.y - shortTick))
            }
            pt.x += 20.0
        }

        tickLayer.path = bez.cgPath
    }
    
}

MidLineView-要覆盖在滚动视图上的垂直线

class MidLineView: UIView {
    
    lazy var midLineLayer: CAShapeLayer = self.layer as! CAShapeLayer
    override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder:aDecoder)
        commonInit()
    }
    
    private func commonInit() {
        midLineLayer.fillColor = nil
        midLineLayer.strokeColor = UIColor.blue.cgColor
        backgroundColor = .clear
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let bez = UIBezierPath()
        
        // we want the mid line to be *about* at the horizontal center
        //  but at an even 20-points
        var x: Int = Int(bounds.midX)
        x -= x % 20

        bez.move(to: .init(x: CGFloat(x), y: bounds.minY))
        bez.addLine(to: .init(x: CGFloat(x), y: bounds.maxY))
        
        midLineLayer.path = bez.cgPath
    }
    
}

ViewController-控制器示例

class ViewController: UIViewController, UIScrollViewDelegate {
    
    let scrollView = UIScrollView()
    
    // view with "tick-mark" lines every 20-points
    let tickView = TickView()
    
    // view with single vertical line
    //  overlay on the scroll view so we have a
    //  "center-line"
    let midLineView = MidLineView()
    
    // track the previous "tick"
    var prevTickMark: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        tickView.translatesAutoresizingMaskIntoConstraints = false
        midLineView.translatesAutoresizingMaskIntoConstraints = false
        
        scrollView.addSubview(tickView)
        view.addSubview(scrollView)
        view.addSubview(midLineView)
        
        let g = view.safeAreaLayoutGuide
        let cg = scrollView.contentLayoutGuide
        let fg = scrollView.frameLayoutGuide
        
        NSLayoutConstraint.activate([
            
            scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            scrollView.heightAnchor.constraint(equalToConstant: 120.0),
            scrollView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            tickView.topAnchor.constraint(equalTo: cg.topAnchor),
            tickView.leadingAnchor.constraint(equalTo: cg.leadingAnchor),
            tickView.trailingAnchor.constraint(equalTo: cg.trailingAnchor),
            tickView.bottomAnchor.constraint(equalTo: cg.bottomAnchor),
            
            // let's make the "tick" view 2000-points wide
            //  so we have a good amount of scrolling distance
            tickView.widthAnchor.constraint(equalToConstant: 2000.0),
            tickView.heightAnchor.constraint(equalTo: fg.heightAnchor, multiplier: 1.0),
            
            midLineView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            midLineView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            midLineView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            midLineView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            
        ])
        
        scrollView.delegate = self
        
        // disable interaction on the overlaid view
        midLineView.isUserInteractionEnabled = false
        
        // so we can see the framing of the scroll view
        scrollView.backgroundColor = .lightGray
        
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        // offsets so the "ticks" start and end near the horiztonal center
        //  on even 20-points
        var x: Int = Int(scrollView.frame.width * 0.5)
        x -= x % 20
        scrollView.contentInset = .init(top: 0.0, left: CGFloat(x), bottom: 0.0, right: scrollView.frame.width - CGFloat(x))
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        var cx: Int = Int(scrollView.contentOffset.x)

        // offset to the first tick-mark
        cx += Int(scrollView.contentInset.left)

        let curTick: Int = cx / 20
        if prevTickMark != curTick {
            // we just passed, or we are on, a new "tick"
            //  so play the tick sound
            AudioServicesPlayAlertSound(SystemSoundID(1057))
            prevTickMark = curTick
        }
    }

}

相关问题