Why is picker view not displaying attributed string?

Why is the callback function pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? not showing the correct font.


Here is my code:


extension ViewController: UIPickerViewDelegate {

    func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
        
        if component == 0 {
            print(keysDataSource[row])
            return keysDataSource[row]
        } else {
            print(chordsDataSource[row])
            return chordsDataSource[row]
        }

    }


Here is the results in the debug window showing the result of the print statement when that callback function is called:


vi{

NSFont = "<UICTFont: 0x113d55700> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 30.00pt";

}


Here's the code to create the attributed strings:


public let fontSizePickerView: CGFloat = 30


func getFont(_ fontType: FontType, size: CGFloat) -> UIFont {
    var fontName: String
    switch fontType {
    case .timesNewRomanRegular:
        fontName = "TimesNewRomanPSMT"
    case .timesNewRomanBold:
        fontName = "TimesNewRomanPS-BoldMT"
    case .maestroWide:
        fontName = "MaestroWide"
    }
    return UIFont(name: fontName, size: size) ?? UIFont.systemFont(ofSize: size)
}


func makeAttributedText(_ text: String, fontSize: CGFloat) -> NSAttributedString? {
    
    print(text)
    
    let nsStringText: NSString = text as NSString
    var nsMutableAttributedText: NSMutableAttributedString = getMutableAttributedString(text, fontSize: fontSize)
    
    let fontSizeDiminished: CGFloat = fontSize * fontSizeRatioDiminished
    let fontSizeFlat: CGFloat = fontSize * fontSizeRatioFlat
    
    makeDiminishedAttribute(text: nsStringText, fontSize: fontSizeDiminished, baselineOffsetRatio: fontDiminishedBaselineOffset, mutableAttributedText: &nsMutableAttributedText)
    makeFlatAttribute(text: nsStringText, fontSize: fontSizeFlat, baselineOffsetRatio: fontFlatBaselineOffset, mutableAttributedText: &nsMutableAttributedText)
    
    return nsMutableAttributedText
    
}

func makeDiminishedAttribute(text: NSString, fontSize: CGFloat, baselineOffsetRatio: Double, mutableAttributedText: inout NSMutableAttributedString) {
    
    var nsRange: NSRange = text.range(of: "L")
    
    guard nsRange.length != 0 else {
        return
    }
    
    let baselineOffset: CGFloat = fontSize * CGFloat(baselineOffsetRatio)
    
    
    let attributesDiminished = getAttributesDiminished(fontSize: fontSize, baselineOffset: baselineOffset)
    
    repeat {
        
        mutableAttributedText.addAttributes(attributesDiminished, range: nsRange)
        if nsRange.location + 1 < text.length {
            let nsRangeToCheck: NSRange = NSRange(location: nsRange.location + 1, length: text.length - (nsRange.location + 1))
            nsRange = text.range(of: "L", range: nsRangeToCheck)
            print(nsRangeToCheck)
            print(nsRange)
        } else {
            break
        }
        
    } while nsRange.length != 0
    
}

func makeFlatAttribute(text: NSString, fontSize: CGFloat, baselineOffsetRatio: Double, mutableAttributedText: inout NSMutableAttributedString) {
    
    var nsRange: NSRange = text.range(of: "b")
    
    guard nsRange.length != 0 else {
        return
    }
    
    let baselineOffset: CGFloat = fontSize * CGFloat(baselineOffsetRatio)
    
    let attributesFlat = getAttributesFlat(fontSize: fontSize, baselineOffset: baselineOffset)
    
    repeat {
        
        mutableAttributedText.addAttributes(attributesFlat, range: nsRange)
        if nsRange.location + 1 < text.length {
            let nsRangeToCheck: NSRange = NSRange(location: nsRange.location + 1, length: text.length - (nsRange.location + 1))
            nsRange = text.range(of: "b", range: nsRangeToCheck)
            print(nsRangeToCheck)
            print(nsRange)
        } else {
            break
        
    } while nsRange.length != 0
    
}
let keysString: [String] = wheel.map() {
    item in
    item.key.name
}
let keysDataSource: [NSAttributedString] = {
    var arrayAttributedString = [NSAttributedString]()
    for string in keysString {
        if let attributedString = makeAttributedText(string, fontSize: fontSizePickerView) {
            arrayAttributedString.append(attributedString)
        }
    }
    return arrayAttributedString
}()
let chordsString: [String] = ["I", "ii", "iii", "IV", "V", "vi", "viiL", "II (V of V)", "III (V of vi)"]
let chordsDataSource: [NSAttributedString] = {
    var arrayAttributedString = [NSAttributedString]()
    for string in chordsString {
        if let attributedString = makeAttributedText(string, fontSize: fontSizePickerView) {
            arrayAttributedString.append(attributedString)
        }
    }
    return arrayAttributedString
}()

Accepted Reply

Effectively, I tested.


Attributes as style do work ; but size is not taken into account.


As you read elsewhere, you have to use viewForRow to get it work (not a big change, you should do this).


Good luck and don't forget to close the thread.


Note: is this your SO post ?

https://stackoverflow.com/questions/55650114/why-is-picker-view-not-displaying-attributed-string

Replies

Do not see where you set the attributedString.


Do you also use


func pickerView(UIPickerView, titleForRow: Int, forComponent: Int) -> String?


In that case, there may be conflict with the other delegte func.

I am not using that function titleForRow. I didn’t include all the code, just the parts that are significant.

OK. So could you post the code where you defined and populated keysDataSource and chordsDataSource

Ok. I just added the code to the end of the original post.


Thanks.

I still do not see how you populate keysDataSource.


Did you try all your functions (as makeAttributedText) in a simple UITextView (not in the picker) to check you effectively get the correct attributed String.


I suspect you have an error there:


func makeAttributedText(_ text: String, fontSize: CGFloat) -> NSAttributedString? {
    
    print(text)
    
    let nsStringText: NSString = text as NSString
    var nsMutableAttributedText: NSMutableAttributedString = getMutableAttributedString(text, fontSize: fontSize)
    
    let fontSizeDiminished: CGFloat = fontSize * fontSizeRatioDiminished
    let fontSizeFlat: CGFloat = fontSize * fontSizeRatioFlat
    
    makeDiminishedAttribute(text: nsStringText, fontSize: fontSizeDiminished, baselineOffsetRatio: fontDiminishedBaselineOffset, mutableAttributedText: &nsMutableAttributedText)
    makeFlatAttribute(text: nsStringText, fontSize: fontSizeFlat, baselineOffsetRatio: fontFlatBaselineOffset, mutableAttributedText: &nsMutableAttributedText)
    
    return nsMutableAttributedText
    
}

Line 11, you set nsMutableAttributedText as diminshed


But at line 12 you set to flat.


What do you really want to return ?

I tried the attributed text on a UILabel, and it worked, though I did not try it on a UITextView.


I forgot to put the code for keyDataSource in the post. I got diverted by some technical difficulties.


The functions makeDiminishedAttribute and makeFlatAttribute each looks at the nsStringText. The makeDiminishedAttribute looks for the character "L" in the nsStringText and assigns attributes to the attributed string at that range. The makeFlatAttribute looks for the character "b" and assigns attribute to the attributed string at that range.


I'll put it there now. And thanks.

OK, I did not look carefully enough at the code.


In the original post, you wrote :


Here is my code:


extension ViewController: UIPickerViewDelegate { 
 
    func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { 
         
        if component == 0 { 
            print(keysDataSource[row]) 
            return keysDataSource[row] 
        } else { 
            print(chordsDataSource[row]) 
            return chordsDataSource[row] 
        } 
 
    }

Here is the results in the debug window showing the result of the print statement when that callback function is called:


vi{

NSFont = "<UICTFont: 0x113d55700> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 30.00pt";

}


But the print should print Strings, not the style definition.


Which print are you talking about ? Not sure it is the one in the picker delegate func.


What are the output for the 2 components ?

P¨lease report exactly what you get here.

The print statements are in the pickerView(_:attributedTitleForRow:_:) callback function.

Did I miss something ?


keysDataSource is an array of Strings.


Does

print(keysDataSource[row])


really print:

vi{

NSFont = "<UICTFont: 0x113d55700> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 30.00pt";

}


????

Surprising…

Here's code from my post. keysDataSource is an array of NSAttributedString.


let keysDataSource: [NSAttributedString] = {  
    var arrayAttributedString = [NSAttributedString]()  
    for string in keysString {  
        if let attributedString = makeAttributedText(string, fontSize: fontSizePickerView) {  
            arrayAttributedString.append(attributedString)  
        }  
    }  
    return arrayAttributedString  
}()


I looked on Stack Overflow and aske the same question there. No one has an answer except a workaround that uses pickerView(_:viewForRow:forComponent:reusing:).

I want to focus on:

    func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { 
         
        if component == 0 { 
            print(keysDataSource[row]) 
            return keysDataSource[row] 
        } else { 
            print(chordsDataSource[row]) 
            return chordsDataSource[row] 
        }

And know exactly what you get in print (the complete log, for all components).

There should be several lines of print.


And could you tell as well what is the content of keysString and probably chordsStrings.

So, could you add somewhere in code

print("keysString", keysString)
print("keysDataSource", keysDataSource)
print("chordsStrings", chordsStrings)
print("chordsDataSource", chordsDataSource)


and report exactly what you get.


PLEASE, answer properly and completely if you want some help.


NOTE: I tested witha picker example, and NSAttributedStrings ; it works perfectly well.

So, show the COMPLETE code of the class where the picker is defined, as well as a copy of the printed log.

Here is the new code I use based on your suggetion. I didn't follow exactly you recommendation, but I got the idea you meant to tell me.


    func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {

        print("attributedTitleForRow")
        
        if component == 0 {
            print("keysString[row]", keysString[row])
            print("keysDataSource[row]", keysDataSource[row])
            return keysDataSource[row]
        } else {
            print("chordsString[row]", chordsString[row])
            print("chordsDataSource[row]", chordsDataSource[row])
            return chordsDataSource[row]
        }

    }


The picker view doesn't show the titles like they are intended for the formatting. It looks like all the attributes doesn't all show. They don't use the designated fonts. A system font is used instead. The baseline offset shows. Too bad I can't show you a screenshot. The print statements print what I expected.


Here are the print results in the debug window when the app first launches:


attributedTitleForRow

keysString[row] C

keysDataSource[row] C{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] G

keysDataSource[row] G{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] I

chordsDataSource[row] I{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] ii

chordsDataSource[row] ii{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] C

keysDataSource[row] C{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] G

keysDataSource[row] G{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] I

chordsDataSource[row] I{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] ii

chordsDataSource[row] ii{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] I

chordsDataSource[row] I{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] ii

chordsDataSource[row] ii{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] iii

chordsDataSource[row] iii{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] IV

chordsDataSource[row] IV{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] V

chordsDataSource[row] V{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] vi

chordsDataSource[row] vi{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

chordsString[row] I

chordsDataSource[row] I{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] C

keysDataSource[row] C{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] G

keysDataSource[row] G{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] D

keysDataSource[row] D{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] A

keysDataSource[row] A{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] Fb/E

keysDataSource[row] F{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}b{

NSBaselineOffset = "10.784375";

NSFont = "<UICTFont: 0x7fa1c6d5e4b0> font-family: \"Maestro Wide\"; font-weight: normal; font-style: normal; font-size: 63.44pt";

}/E{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] Cb/B

keysDataSource[row] C{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}b{

NSBaselineOffset = "10.784375";

NSFont = "<UICTFont: 0x7fa1c6d5e4b0> font-family: \"Maestro Wide\"; font-weight: normal; font-style: normal; font-size: 63.44pt";

}/B{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

attributedTitleForRow

keysString[row] C

keysDataSource[row] C{

NSFont = "<UICTFont: 0x7fa1c6d5e170> font-family: \"Times New Roman\"; font-weight: normal; font-style: normal; font-size: 58.00pt";

}

Effectively, I tested.


Attributes as style do work ; but size is not taken into account.


As you read elsewhere, you have to use viewForRow to get it work (not a big change, you should do this).


Good luck and don't forget to close the thread.


Note: is this your SO post ?

https://stackoverflow.com/questions/55650114/why-is-picker-view-not-displaying-attributed-string

Yes. That is my Stack Overflow post. I got the same answer from someone as your answer. That seems like a workaround instead of the real solution. It looks like attributedTitleForRow doesn't work like it's supposed to. I haven't gotten any answer that makes it work.