본문 바로가기

IOS

SwiftUI - TextView Attribute

회사일로 지쳐 오랜만에 글을 써보네요.. 

 

이어 붙여져있는 Text 에 대해 각각의 색상 변경과 클릭이벤트를 주고 싶었으나 

뭥미..

다음과 같이 반환값이 맞춰지지 않아 Text 끼리 색상 및 옵션 변경은 가능하나, 클릭이벤트를 줄수 없습니다.

그래서 Attribute 를 이용해서 만들고자 합니다 (대부분의 Attribute는 링크를 이용해 웹페이지를 열곤 하는데 저는 다른 작업이 하고 싶어 이런식으로 만듭니다)


1. UIViewRepresentable 

결국은 UIKit의 힘을 빌려야 하기에 UIViewRepresentable 를 이용하고, 클릭했을때 URL 값을 얻기 위해 Coordinator 를 이용합니다.

전체 글꼴의 대한 attribute를 처리후 배열로 전달받았던 attribute 를 처리 하기 위한 텍스트로 처리합니다. 여기서 링크는 처리했던 텍스트로 처리 합니다 (URL 를 처리 하는것처럼, 공백 및 특수문자가 들어갈경우 오류가 생길수 있으니, 정규식 처리 및 replace 처리가 필요합니다.) TextView 생성후 높이를 바인딩 통해 View에 전달 합니다.

struct AttributeTextView: UIViewRepresentable {
    @Binding var fullText: String
    @Binding var height: Double
    @State var attributeText: [String]
    
    let action: (String) -> Void

    func makeCoordinator() -> Coordinator {
        Coordinator(self, action)
    }

    func makeUIView(context: Context) -> UITextView {
        let attributedText = NSMutableAttributedString(string: fullText)

        let standartTextAttributes: [NSAttributedString.Key : Any] = [
            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14),
            NSAttributedString.Key.foregroundColor: UIColor.gray
        ]

        attributedText.addAttributes(standartTextAttributes, range: attributedText.range)

        for i in 0..<attributeText.count {
            let hyperlinkTextAttributes: [NSAttributedString.Key : Any] = [
                NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14),
                NSAttributedString.Key.foregroundColor: UIColor.red,
                NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
                NSAttributedString.Key.link: attributeText[i].replacingOccurrences(of: " ", with: "")
            ]

            attributedText.addAttributes(hyperlinkTextAttributes, range: (attributedText.string as NSString).range(of: attributeText[i]))
        }
        
        let textView = UITextView()
        textView.attributedText = attributedText
        
        textView.textAlignment = .left
        textView.delegate = context.coordinator
        textView.isEditable = false
        textView.isSelectable = true
        textView.isScrollEnabled = true
        textView.isUserInteractionEnabled = true
      
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        DispatchQueue.main.async {
            height = uiView.contentSize.height
        }
    }
 }

2. Coordinator

UIViewRepresentable 상에서 링크로 처리했던 value 값이 shouldInteractWith function 으로 넘어옵니다. 해당 액션을 View 단에서 처리 하기위해 클로저를 이용해 url 값과 함께 전달 합니다.

class Coordinator : NSObject, UITextViewDelegate {

        var parent: AttributeTextView
        let action: (String) -> Void

        init(_ uiTextView: AttributeTextView, _ action: @escaping (String) -> Void) {
            self.parent = uiTextView
            self.action = action
        }

        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            return true
        }

        func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
            action(URL.absoluteString)
            return false
       }
    }

3. View 

전체 텍스트와 attribute 처리할 텍스트, 높이값 을 전달합니다 클릭했을때 Delegate 액션 처리한 URL 값을 수신받을수 있습니다.

 

import SwiftUI

struct ContentView: View {
    @State var fullText = "You can go to stack overflow site end enjoy it using old-school UITextView and UIViewRepresentable"
    @State var attributeText = ["old-school", "UIViewRepresentable"]
    @State var height: Double = 0
  
    var body: some View {
        
        VStack {
            
            AttributeTextView(fullText: $fullText, height: $height, attributeText: attributeText) {
                print($0)
            }.frame(height: height)
            
            Spacer()
            
        }
    }
}

 


4.  전체소스

import SwiftUI

struct ContentView: View {
    @State var fullText = "You can go to stack overflow site end enjoy it using old-school UITextView and UIViewRepresentable"
    @State var attributeText = ["old-school", "UIViewRepresentable"]
    @State var height: Double = 0
  
    var body: some View {
        
        VStack {
            
            AttributeTextView(fullText: $fullText, height: $height, attributeText: attributeText) {
                print($0)
            }.frame(height: height)
            
            Spacer()
            
        }
    }
}

struct AttributeTextView: UIViewRepresentable {
    @Binding var fullText: String
    @Binding var height: Double
    @State var attributeText: [String]
    
    let action: (String) -> Void

    func makeCoordinator() -> Coordinator {
        Coordinator(self, action)
    }

    func makeUIView(context: Context) -> UITextView {
        let attributedText = NSMutableAttributedString(string: fullText)

        let standartTextAttributes: [NSAttributedString.Key : Any] = [
            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14),
            NSAttributedString.Key.foregroundColor: UIColor.gray
        ]

        attributedText.addAttributes(standartTextAttributes, range: attributedText.range)

        for i in 0..<attributeText.count {
            let hyperlinkTextAttributes: [NSAttributedString.Key : Any] = [
                NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14),
                NSAttributedString.Key.foregroundColor: UIColor.red,
                NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
                NSAttributedString.Key.link: attributeText[i].replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "‘", with: "").replacingOccurrences(of: "’", with: "")
            ]

            attributedText.addAttributes(hyperlinkTextAttributes, range: (attributedText.string as NSString).range(of: attributeText[i]))
        }
        
        let textView = UITextView()
        textView.attributedText = attributedText
        
        textView.textAlignment = .left
        textView.delegate = context.coordinator
        textView.isEditable = false
        textView.isSelectable = true
        textView.isScrollEnabled = true
        textView.isUserInteractionEnabled = true
      
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        DispatchQueue.main.async {
            height = uiView.contentSize.height
        }
    }
    
    class Coordinator : NSObject, UITextViewDelegate {

        var parent: AttributeTextView
        let action: (String) -> Void

        init(_ uiTextView: AttributeTextView, _ action: @escaping (String) -> Void) {
            self.parent = uiTextView
            self.action = action
        }

        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            return true
        }

        func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
            action(URL.absoluteString)
            return false
       }
    }
}

 

참고 : https://stackoverflow.com/questions/56471973/ho

 

How do I create a multiline TextField in SwiftUI?

I've been trying to create a multiline TextField in SwiftUI, but I can't figure out how. This is the code I currently have: struct EditorTextView : View { @Binding var text: String var...

stackoverflow.com

w-do-i-create-a-multiline-textfield-in-swiftui/58639072#58639072

 

How do I create a multiline TextField in SwiftUI?

I've been trying to create a multiline TextField in SwiftUI, but I can't figure out how. This is the code I currently have: struct EditorTextView : View { @Binding var text: String var...

stackoverflow.com

https://zeddios.tistory.com/462

 

iOS ) 일치하는 모든 문자열의 Attribute를 바꾸고 싶을 때

안녕하세요 :) Zedd입니다. 오늘...!어떤 분이 질문을 주셨어요. 제가 저 때 컴퓨터 앞이 아니라서...일단 해봐야 할 것 같다고 말씀드렸는데, 참 해볼만한 주제? 죠? 저분이 보신 글은 이에요. 이런

zeddios.tistory.com

https://www.mmbyte.com/article/76821.html

 

SwiftUI tappable subtext - 漫漫字节|漫漫编程

Is there any way in SwiftUI to open browser, when tapping on some part of the text. I tried the above solution but it doesn't work because onTapGesture returns View which you cannot add to Text Text("Some text ").foregroundColor(Color(UIColor.systemGray))

www.mmbyte.com

 

'IOS' 카테고리의 다른 글

Swift - async await  (0) 2022.10.26
SwiftUI - List Hide Indicator  (0) 2022.10.23
Swift - Snapkit  (0) 2022.04.10
SwiftUI - SNS 공유하기 (Facebook, Twitter, WhatsApp, Viber)  (0) 2022.02.27
Xcode - info.plist 파일이 없어졌을때 해결  (0) 2022.02.24