Search code examples
swiftmacosuser-interfaceswiftuinsmutableattributedstring

How to Display NSMutableAttributedString with updates in SwiftUI


I want a NSMutableAttributedString do display a String where only on char has an other Color. After seraching i just found a examples to use NSMutableAttributedString on IOS but nothing suits for me.

import SwiftUI

struct SpritzViewMac: View {
    
    var spritz = Spritz(Eingabe:"Welcome to SpritzSwift! Spritz is a brand new revolutionary reading method that will help you to improve your number of words per minute. Take a look at SpritzSwift!", wordsPerMinute: 400)
    
    @State var spritzWordForText = NSMutableAttributedString(string: "Test")
 
    func updateWord(_ wordInput: SpritzWord){
        let attributs = [NSMutableAttributedString.Key.foregroundColor: markerColor]
        spritzWordForText = NSMutableAttributedString(string: wordInput.word)
        spritzWordForText.setAttributes(attributs, range: NSRange(location: wordInput.markerPosition, length: 1))
    }
    
    var body: some View {
     
        VStack {
            *display "spritzWordForText" with Attributs*
            }
        }
    }
}

Basically I want to create a kind of "Text()" that works like a spritz program

"updateWord" is called repeatedly and gets each time a new word with the corresponding letter to be marked. Currently I overwrite "spritzWordForText" in "updateWord" each time and add the new, matching attribute. So before each update of the text both the string value must be renewed and the attribute must be recalculated.

I have already tried several versions with NSViewRepresentable, but when updating the program crashes every time

In addition, a few classes, but it often turns out that you can use them only from IOS.


Solution

  • IMO you don't need attributed string at all: just create a view that will display each letter in a separate Text, with its color dependent on whether the letter is highlighted or not. Something like this:

    struct WordView: View {
        
        let word: [Character]
        let markerPosition: Int
        
        init(word: String, markerPosition: Int) {
            self.word = Array(word) // for convenience of `ForEach` used below
            self.markerPosition = markerPosition
        }
        
        var body: some View {
            HStack(spacing: 0) { // we want all letters horizontally, close to each other
                ForEach(0..<word.count) { index in
                    Text(String(word[index]))
                        .foregroundColor(index == markerPosition ? Color.red : Color.blue) // change background for highlighted letter
                }
            }
        }
    }
    

    And so you use it like this:

    struct TestView: View {
        
        var body: some View {
            WordView(word: "Test", markerPosition: 2)
        }
    }