How do I rewrite a UILabel after it is added to a

2019-03-03 18:48发布

问题:

The problem is now fixed. This answer shows an audio application based on the working solution.

Initial question

I am new to Swift and trying to create a bank of UISliders to test parameters of a physical model in AudioKit. Each UISlider has two UILabels, one to identify the name of a parameter, the other to show a current UISlider value. Tags identify each UISlider and its corresponding UIlabels.

I am stuck trying to display current UISlider values in the corresponding UILabel on an iPhone although I can display these in the debug area in Xcode. When I write slider.value to its lableForValue nothing happens except a weird edge condition (see diagram at the bottom).

A log of UISlider values clearly showed it receiving a value sent and using sender.tag to identify which UISlider sent it. But the new value would never appear in the correct UILabel.

Solution

Here is a working solution that will hopefully benefit some other Swift novice. Changes based on the accepted answer have been made to the code below. Tagging lableForValue with a tag offset before adding it to subview allowed UILabels to be more easily identified and rewritten with values read from UISlider. The accepted answer is also a simple practical demonstration of how to use optionals. A further edge condition has been identified - UILabels would display values for all sliders except the first - and is corrected here in the final edit. The code also includes an extension of UILabel used to change the font size.

Thank you PiyushRathi and dijipiji


Final Edit

import UIKit

class ViewController: UIViewController {

var slider: UISlider!
var lableForValue: UILabel!
var lableForID: UILabel!

let defaultColour       = UIColor.green
let highlightedColour   = UIColor.lightGray

let thumbSize: CGFloat  = 20
let topMargin           = 75
let verticalSpacing     = 50
let sliderWidth         = 250
let sliderHeight        = 24
let sliderToLabelSpace  = 32

let valueLableTagOffset = 1000

let lables              = ["intensity", 
                           "dampingFactor", 
                           "energyReturn", 
                           "mainResFreq", 
                           "1stResFreq", 
                           "2ndResFreq", 
                           "amplitude", 
                           "reserved", 
                           "reserved", 
                           "reserved"]

let loLimits            = [0,   0,   0,   0,   0,   0,   0,   0,   0,   0]
let hiLimits            = [100, 100, 100, 100, 100, 100, 100, 100, 100, 100]


override func viewDidLoad() {
    super.viewDidLoad()

    for index in 0..<10 {

        let slider      = makeSlider(index: index)
        let IDLable     = makeIDLable(index: index)
        let valueLable  = makeValueLable(index: index)

        view.addSubview(slider)
        view.addSubview(IDLable)
        view.addSubview(valueLable)
        }

    }


func sliderValueChanged(sender: UISlider){

print("SLIDER", sender.tag, ":", sender.value)

    var valueLabel: UILabel? = nil

    for subview in view.subviews as [UIView] {

        if subview.tag > valueLableTagOffset {
            print(subview.tag)

            let labelTag = subview.tag - valueLableTagOffset

     // Edge condition: UILabels display values for all sliders except the first 
     // Fix: use '- valueLableTagOffset', not '/ valueLableTagOffset'

            print(labelTag)

            if labelTag == sender.tag {

                valueLabel = subview as? UILabel
                break
            }
        }
    }

    if valueLabel != nil {
        valueLabel!.text = String(sender.value)
    }
}


func makeHighlightedImage() -> (UIImage) {
    let size                        = thumbSize
    let highlightedStateImage       = UIImage.createThumbImage(size: size, color: highlightedColour)
    return (highlightedStateImage)
    }

func makeDefaultImage() -> (UIImage) {
    let size                        = thumbSize
    let defaultStateImage           = UIImage.createThumbImage(size: size, color: defaultColour)
    return (defaultStateImage)
    }


func makeValueLable(index: Int) -> UILabel {
    let x                           = Int(view.frame.midX) - (sliderWidth / 2)
    let y                           = Int(topMargin + (verticalSpacing * index) - sliderToLabelSpace)
    let w                           = sliderWidth
    let h                           = sliderHeight

    lableForValue                   = UILabel(frame: CGRect(x: x, y: y, width: w, height: h))

    lableForValue.tag               = (index + 1) + valueLableTagOffset

     // Edge condition: UILabels display values for all sliders except the first 
     // Fix: use '+ valueLableTagOffset', not '* valueLableTagOffset'

    lableForValue.textColor         = defaultColour
    lableForValue.textAlignment     = NSTextAlignment.center
    lableForValue.text              = String(index + 1)
    return lableForValue
    }


func makeIDLable(index: Int) -> UILabel {
    let x                           = Int(view.frame.midX) - (sliderWidth / 2)
    let y                           = Int(topMargin + (verticalSpacing * index) - 32)
    let w                           = sliderWidth
    let h                           = sliderHeight

    lableForID                      = UILabel(frame: CGRect(x: x, y: y, width: w, height: h))
    lableForID.tag                  = index + 1
    lableForID.textColor            = highlightedColour
    lableForID.textAlignment        = NSTextAlignment.left
    lableForID.defaultFont          = UIFont(name: "HelveticaNeue", size: CGFloat(12))
    lableForID.text                 = lables[index]
    return lableForID
    }

func makeSlider(index: Int) -> UISlider {
    let x                           = view.frame.midX
    let y                           = CGFloat(topMargin + (verticalSpacing * index))
    let w                           = sliderWidth
    let h                           = sliderHeight

    slider                          = UISlider(frame: CGRect(x: 0, y: 0, width: w, height: h))
    slider.center                   = CGPoint(x: x, y: y)

    slider.minimumValue             = Float(loLimits[index])
    slider.minimumTrackTintColor    = defaultColour
    slider.maximumValue             = Float(hiLimits[index])
    slider.maximumTrackTintColor    = highlightedColour
    slider.tag                      = index + 1

    slider.value                    = slider.maximumValue / 2.0

    slider.isContinuous             = false
    slider.addTarget(self, action: #selector(sliderValueChanged), for: UIControlEvents.valueChanged)
    return slider
    }
}

UILabel+FontFiddler

This extension is necessary to get a different sized font for labelForID and labelForValue

thank you Oleg Sherman

import UIKit

extension UILabel{
    var defaultFont: UIFont? {
        get { return self.font }
        set { self.font = newValue }
        }
    }

Edge Condition

The screen-shot below shows what happens to the last UILabel when any slider is moved. The value displayed is always 50.0 no matter which slider is moved or how far. I do know the condition disappears when I disable the statement that reads a value from slider 10. But I can't tell how a value of 50 always appears in the UILabel for slider 10 whenever other sliders are moved.

回答1:

Hi you have to do several changes in your code:

  1. You have to pass a unique tag value to each lableForValue so it can be found easily in UIView.

    e.g. for adding label in func makeValueLable(index: Int) -> UILabel function put lableForValue.tag = (index + 1) * 1000

  2. Change func sliderValueChanged(sender: UISlider){ to this:

    func sliderValueChanged(sender: UISlider) {
    
            var valueLabel:UILabel? = nil;
            for subview in view.subviews as [UIView] {
                if subview.tag > 1000 {
    
                    let labelTag = subview.tag / 1000
                    if labelTag == sender.tag {
                        valueLabel = subview as? UILabel
                        break
                    }
                }
            }
            if valueLabel != nil {
                valueLabel!.text = String(sender.value)
            }
        }
    

hope this helps.



回答2:

It looks like you have these member variables: var lableForValue: UILabel! var lableForID: UILabel!

In your func makeValueLable you should consider creating a fresh label instead of referencing the member variables. In short - ditch you member variables for these UILabels and inside makeValueLable and makeIDLabel create fresh instances: let labelForValue = UILabel() let lablelFotID = UILabel() etc.

Note - you spell "label" as "lable" quite a bit :)



回答3:

Here is how UISliders may be used to change parameters in physical modelling audio synthesis. You will need AudioKit.framework. Instructions for downloading and using it can be found here.

The model is essentially a chaotic system for synthesising sounds made by dripping water. Some sliders have more effect than others but it is dampingFactor that excites the physical model. At first an isolated drip sound can be heard when this is changed, but, like standard plumbing fixtures, if you fiddle with it long enough you will have a steady flow of dripping sounds that may be difficult (but not impossible) to stop. Three sliders for resonant frequency affect the pitch of the sound.

import UIKit
import AudioKit

class ViewController: UIViewController {

let drip            = AKDrip()
var timer           = Timer()

var slider: UISlider!
var lableForValue: UILabel!
var lableForID: UILabel!

let defaultColour       = UIColor.green
let highlightedColour   = UIColor.lightGray

let thumbSize: CGFloat  = 20
let topMargin           = 75
let verticalSpacing     = 50
let sliderWidth         = 250
let sliderHeight        = 24
let sliderToLabelSpace  = 38

let valueLableTagOffset = 1000

let lables              = ["intensity",
                           "dampingFactor",
                           "energyReturn",
                           "mainResFreq",
                           "1stResFreq",
                           "2ndResFreq",
                           "amplitude",
                           "rampTime",
                           "reserved",
                           "reserved"]

let loLimits            = [0,     0,     0,     0,      0,      0,      0,     0,     0,    0]
let hiLimits            = [100,   100,   100,   1000,   1000,   1000,   100,   100,   100,  100]
//                        [10.0,  2.9,   5.0,   750.0,  450.0,  600.0,  0.5,   1.0,   0,    0]


override func viewDidLoad() {
    super.viewDidLoad()

    for index in 0..<10 {

        let slider      = makeSlider(index: index)
        let IDLable     = makeIDLable(index: index)
        let valueLable  = makeValueLable(index: index)

        view.addSubview(slider)
        view.addSubview(IDLable)
        view.addSubview(valueLable)

    }

    // Physical Model Oscillator

    AudioKit.output = drip
    AudioKit.start()

    drip.start()        
}


func sliderValueChanged(sender: UISlider){

    print("SLIDER", sender.tag, ":", sender.value)

    var valueLabel: UILabel? = nil                              // clear UILabel

    for subview in view.subviews as [UIView] {                  // find all subviews including labels

        if subview.tag > valueLableTagOffset {                  // identify labels that show values
            print(subview.tag)

            let labelTag = subview.tag - valueLableTagOffset    // get true tag by removing offset
            print(labelTag)

            if labelTag == sender.tag {                         // does tag match the slider that moved ?

                valueLabel = subview as? UILabel                // then this subview is the value label

                break
            }
        }
    }

    if valueLabel != nil {
        valueLabel!.text    = String(sender.value)              // so write slider value into its label
        let paramValue      = sender.value
        let paramID         = sender.tag
        setDrip(paramValue: paramValue, paramID: paramID)       // and write slider value into parameter
        }
    }

func setDrip(paramValue: Float, paramID: Int) {
    switch paramID {
    case 0:
        drip.intensity                  = Double(paramValue)    //10
    case 1:
        drip.dampingFactor              = Double(paramValue)    //2.9
    case 2:
        drip.energyReturn               = Double(paramValue)    //5
    case 3:
        drip.mainResonantFrequency      = Double(paramValue)    //750
    case 4:
        drip.firstResonantFrequency     = Double(paramValue)    //450
    case 5:
        drip.secondResonantFrequency    = Double(paramValue)    //600
    case 6:
        drip.amplitude                  = Double(paramValue)    //0.5
    case 7:
        drip.rampTime                   = Double(paramValue)    //1.0

    default:
        print("nothing to change for sliders 8 & 9")
        }    
    }


func makeHighlightedImage() -> (UIImage)    {
    let size                        = thumbSize
    let highlightedStateImage       = UIImage.createThumbImage(size: size, color: highlightedColour)
    return (highlightedStateImage)
    }

func makeDefaultImage() -> (UIImage)        {
    let size                        = thumbSize
    let defaultStateImage           = UIImage.createThumbImage(size: size, color: defaultColour)
    return (defaultStateImage)
}


func makeValueLable(index: Int) -> UILabel  {
    let x                           = Int(view.frame.midX) - (sliderWidth / 2)
    let y                           = Int(topMargin + (verticalSpacing * index) - sliderToLabelSpace)
    let w                           = sliderWidth
    let h                           = sliderHeight

    lableForValue                   = UILabel(frame: CGRect(x: x, y: y, width: w, height: h))
    lableForValue.tag               = (index + 1) + valueLableTagOffset
    lableForValue.textColor         = defaultColour
    lableForValue.textAlignment     = NSTextAlignment.center
    lableForValue.text              = String(index + 1)
    return lableForValue
    }

func makeIDLable(index: Int) -> UILabel     {
    let x                           = Int(view.frame.midX) - (sliderWidth / 2)
    let y                           = Int(topMargin + (verticalSpacing * index) - 32)
    let w                           = sliderWidth
    let h                           = sliderHeight

    lableForID                      = UILabel(frame: CGRect(x: x, y: y, width: w, height: h))
    lableForID.tag                  = index + 1
    lableForID.textColor            = highlightedColour
    lableForID.textAlignment        = NSTextAlignment.left
    lableForID.defaultFont          = UIFont(name: "HelveticaNeue", size: CGFloat(12))
    lableForID.text                 = lables[index]
    return lableForID
    }

func makeSlider(index: Int) -> UISlider     {
    let x                           = view.frame.midX
    let y                           = CGFloat(topMargin + (verticalSpacing * index))
    let w                           = sliderWidth
    let h                           = sliderHeight

    slider                          = UISlider(frame: CGRect(x: 0, y: 0, width: w, height: h))
    slider.center                   = CGPoint(x: x, y: y)

    slider.minimumValue             = Float(loLimits[index])
    slider.minimumTrackTintColor    = defaultColour
    slider.maximumValue             = Float(hiLimits[index])
    slider.maximumTrackTintColor    = highlightedColour
    slider.tag                      = index + 1

    if (lables[index] != "reserved") {

        slider.value                = slider.maximumValue / 2.0
        slider.isContinuous         = false
        slider.addTarget(self, action: #selector(sliderValueChanged), for: UIControlEvents.valueChanged)

    } else {
        slider.value                = 0
    }
    return slider
    }
}

Extension 1 UILabel+FontFiddler

import UIKit

extension UILabel{
var defaultFont: UIFont? {
    get { return self.font }
    set { self.font = newValue }
    }
}

Extension 2 UIImage+DrawCircle

Thank you McMatan

import UIKit

extension UIImage {

class func createThumbImage(size: CGFloat, color: UIColor) -> UIImage {

    let layerFrame          = CGRect(x: 0, y: 0, width: size, height: size)

    let shapeLayer          = CAShapeLayer()
    shapeLayer.path         = CGPath(ellipseIn: layerFrame.insetBy(dx: 1, dy: 1), transform: nil)
    shapeLayer.fillColor    = color.cgColor
    shapeLayer.strokeColor  = color.withAlphaComponent(0.65).cgColor

    let layer               = CALayer.init()
    layer.frame             = layerFrame
    layer.addSublayer(shapeLayer)
    return self.imageFromLayer(layer: layer)

}

class func imageFromLayer(layer: CALayer) -> UIImage {

    UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, UIScreen.main.scale)
    layer.render(in: UIGraphicsGetCurrentContext()!)
    let outputImage         = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return outputImage!
    }
}