Yousif Ismat
Yousif Ismat

Reputation: 31

Making Pointer that reads string while over it - SwiftUI

I want to make the following behavior possible in SwiftUI but i dont know how:

Imagine having a Text that contains a string (or attributed string), also you have a pointer that can be hovered over the letters of the string like a mouse icon (or any icon), as the pointer goes on a letter, that letter gets highlighted (i.e colors differently).

What i dont want: I dont want you to use different Texts and add them up using an HStack and Foreach, i want it to be all together, since i want it work for all languages and the method mentioned works for language that have unconnected letters only.

is it possible?

Upvotes: 0

Views: 142

Answers (1)

KerryDong
KerryDong

Reputation: 66

Yes, using NSAttributedString, NSTextAttachment can achieve your needs.

I encapsulate it in HighlightWithCursorLabel:

struct HighlightWithCursorLabel: UIViewRepresentable {
    var originText: String
    var color: UIColor
    @Binding var highlightIndex: Int
    
    var configuration = { (view: UILabel) in }
                
    init(originText: String, color: UIColor, highlightIndex: Binding<Int>, configuration: @escaping (UILabel) -> () = { _ in }) {
        self.originText = originText
        self.color = color
        self._highlightIndex = highlightIndex
        self.configuration = configuration
    }
    
    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        label.text = originText
        label.textColor = UIColor(Colors.subText)
        return label
    }

    func updateUIView(_ uiView: UILabel, context: Context) {
        configuration(uiView)
        
        if highlightIndex > originText.count { return }
        
        let attributString = NSMutableAttributedString(string: originText)
        
        let nsRange = NSRange(location: 0, length: highlightIndex)
        let attribute = [NSAttributedString.Key.foregroundColor: color]
        attributString.addAttributes(attribute, range: nsRange)

        let attachment = NSTextAttachment(image: UIImage(systemName: "cursorarrow")!.withTintColor(color)) // replace it to any UIImage
        let attachmentString = NSAttributedString(attachment: attachment)

        attributString.insert(attachmentString, at: highlightIndex)
        uiView.attributedText = attributString
    }
}

Usage

struct HighlightSampleView: View {
    @State private var highlightIndex: Int = 0
    private var text = "Hello World~~~~~"
    
    var body: some View {
        HighlightWithCursorLabel(originText: text, color: UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1.0), highlightIndex: $highlightIndex)
            .onAppear {
                highlightText()
            }
    }
    
    // To show the highlight effect, replace it when you want to update index
    private func highlightText() {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
            highlightIndex += 1
            
            if highlightIndex > text.count {
                timer.invalidate()
            }
        }
    }
}

struct HighlightSampleView_Previews: PreviewProvider {
    static var previews: some View {
        HighlightSampleView()
    }
}

Result

enter image description here enter image description here

Upvotes: 0

Related Questions