How do I rewrite a UILabel after it is added to a subView?
Asked Answered
M

3

2

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.

enter image description here

Macfadyn answered 14/12, 2016 at 8:55 Comment(3)
I thought you need to display slider changed values in label?Eosin
In your code, when slider value change, you directly assign string to lableForValue, which is last label you have added to View, You need to first take Label according to slider Tag, and then assign String to it.Eosin
@PiyushRathi, Piyush, I think you have identified the problem. See my edit and if you submit an answer with a solution I will accept it. ThanksMacfadyn
E
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.

Eosin answered 15/12, 2016 at 5:37 Comment(3)
great! It works perfectly using subview.tag / 1000 rather than subview.tag % 1000. This is the accepted answer. Much appreciated.Macfadyn
I discovered an edge condition but it is now fixed - all sliders changed their values except the first. The fix is to add 1000 as an offset while writing the tag and subtract the offset while reading the tag in func sliderValueChanged(sender: UISlider)Macfadyn
I’ve added the audio application. You might be interested. https://mcmap.net/q/376576/-how-do-i-rewrite-a-uilabel-after-it-is-added-to-a-subview/2348597Macfadyn
S
0

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 :)

Samathasamau answered 14/12, 2016 at 9:35 Comment(6)
You need to send through the UISlider as a parameter like this:Samathasamau
You need to send through the UISlider as a parameter like this: slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: UIControlEvents.valueChanged)Samathasamau
Try: slider.addTarget(self, action: #selector(sliderValueChanged(sender:)), for: UIControlEvents.valueChanged)Samathasamau
Or in sliderValueChanged do : func sliderValueChanged(_ sender: UISlider) to make it optionalSamathasamau
dijipiji, see my latest edit. Piyush may have identified the problem. If you too would like to submit an answer with a solution I will accept it. The SO moderator has asked to move our discussion to chat but I don't think that will be necessary. Thank you for your comments. They have been helpful.Macfadyn
I’ve added the audio application. You might be interested. https://mcmap.net/q/376576/-how-do-i-rewrite-a-uilabel-after-it-is-added-to-a-subview/2348597Macfadyn
M
0

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!
    }
}
Macfadyn answered 21/12, 2016 at 3:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.