iOS의 UITextView에서 텍스트에 대한 탭 감지
나는이 UITextView을 표시하는가 NSAttributedString. 이 문자열에는 탭 가능하게 만들고 싶은 단어가 포함되어 있습니다. 예를 들어 탭하면 다시 전화를 걸어 작업을 수행 할 수 있습니다. 그 실현 UITextView의 URL에 도청 장치를 검색 및 대리자를 다시 호출 할 수 있지만 이러한 URL이 없습니다.
iOS 7과 TextKit의 힘으로 이것이 가능해야하지만 예제를 찾을 수 없으며 어디서부터 시작 해야할지 잘 모르겠습니다.
문자열에 사용자 정의 속성을 만들 수 있음을 이해하고 있습니다 (아직 아직하지는 않았지만). 마법 단어 중 하나가 도청되었는지 감지하는 데 유용 할 수 있습니까? 어쨌든, 나는 여전히 그 탭을 가로 채고 탭이 발생한 단어를 감지하는 방법을 모르겠습니다.
iOS 6 호환성은 필요 하지 않습니다.
나는 단지 다른 사람들을 조금 더 돕고 싶었습니다. Shmidt의 답변에 따라 원래 질문에서 요청 한대로 정확하게 할 수 있습니다.
1) 클릭 가능한 단어에 적용되는 사용자 정의 속성을 사용하여 귀중한 문자열을 작성하십시오. 예.
NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a clickable word" attributes:@{ @"myCustomTag" : @(YES) }];
[paragraph appendAttributedString:attributedString];
2) 해당 문자열을 표시 할 UITextView를 작성하고 UITapGestureRecognizer를 추가하십시오. 그런 다음 탭을 처리하십시오.
- (void)textTapped:(UITapGestureRecognizer *)recognizer
{
UITextView *textView = (UITextView *)recognizer.view;
// Location of the tap in text-container coordinates
NSLayoutManager *layoutManager = textView.layoutManager;
CGPoint location = [recognizer locationInView:textView];
location.x -= textView.textContainerInset.left;
location.y -= textView.textContainerInset.top;
// Find the character that's been tapped on
NSUInteger characterIndex;
characterIndex = [layoutManager characterIndexForPoint:location
inTextContainer:textView.textContainer
fractionOfDistanceBetweenInsertionPoints:NULL];
if (characterIndex < textView.textStorage.length) {
NSRange range;
id value = [textView.attributedText attribute:@"myCustomTag" atIndex:characterIndex effectiveRange:&range];
// Handle as required...
NSLog(@"%@, %d, %d", value, range.location, range.length);
}
}
방법을 알면 너무 쉽습니다!
Swift로 중요한 텍스트의 탭 감지
때때로 초보자에게는 상황을 설정하는 방법을 아는 것이 약간 어렵습니다 (어쨌든 나를 위해있었습니다).이 예제는 조금 더 가득합니다.
UITextView프로젝트에를 추가 하십시오.

콘센트
연결 UITextView받는 사람을 ViewController라는 콘센트 textView.
맞춤 속성
우리는 Extension 을 만들어서 커스텀 속성을 만들 것 입니다.
참고 : 이 단계는 기술적으로 선택 사항이지만 그렇지 않은 경우 다음과 같은 표준 속성을 사용하도록 다음 부분의 코드를 편집해야합니다
NSAttributedString.Key.foregroundColor. 사용자 정의 속성을 사용하면 이점이있는 텍스트 범위에 저장할 값을 정의 할 수 있습니다.
File> New> File ...> iOS> Source> Swift File 로 새 신속한 파일을 추가하십시오 . 원하는대로 부를 수 있습니다. NSAttributedStringKey + CustomAttribute.swift을 호출하고 있습니다.
다음 코드를 붙여 넣습니다.
import Foundation
extension NSAttributedString.Key {
static let myAttributeName = NSAttributedString.Key(rawValue: "MyCustomAttribute")
}
암호
ViewController.swift의 코드를 다음과 같이 바꾸십시오. 를 참고하십시오 UIGestureRecognizerDelegate.
import UIKit
class ViewController: UIViewController, UIGestureRecognizerDelegate {
@IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Create an attributed string
let myString = NSMutableAttributedString(string: "Swift attributed text")
// Set an attribute on part of the string
let myRange = NSRange(location: 0, length: 5) // range of "Swift"
let myCustomAttribute = [ NSAttributedString.Key.myAttributeName: "some value"]
myString.addAttributes(myCustomAttribute, range: myRange)
textView.attributedText = myString
// Add tap gesture recognizer to Text View
let tap = UITapGestureRecognizer(target: self, action: #selector(myMethodToHandleTap(_:)))
tap.delegate = self
textView.addGestureRecognizer(tap)
}
@objc func myMethodToHandleTap(_ sender: UITapGestureRecognizer) {
let myTextView = sender.view as! UITextView
let layoutManager = myTextView.layoutManager
// location of tap in myTextView coordinates and taking the inset into account
var location = sender.location(in: myTextView)
location.x -= myTextView.textContainerInset.left;
location.y -= myTextView.textContainerInset.top;
// character index at tap location
let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
// if index is valid then do something.
if characterIndex < myTextView.textStorage.length {
// print the character index
print("character index: \(characterIndex)")
// print the character at the index
let myRange = NSRange(location: characterIndex, length: 1)
let substring = (myTextView.attributedText.string as NSString).substring(with: myRange)
print("character at index: \(substring)")
// check if the tap location has a certain attribute
let attributeName = NSAttributedString.Key.myAttributeName
let attributeValue = myTextView.attributedText?.attribute(attributeName, at: characterIndex, effectiveRange: nil)
if let value = attributeValue {
print("You tapped on \(attributeName.rawValue) and the value is: \(value)")
}
}
}
}
"Swift"의 "w"를 탭하면 다음과 같은 결과가 나타납니다.
character index: 1
character at index: w
You tapped on MyCustomAttribute and the value is: some value
노트
- 여기서는 사용자 정의 속성을 사용했지만
NSAttributedString.Key.foregroundColor값 이 인 (텍스트 색상) 처럼 쉽게 사용할 수 있습니다UIColor.green. - 이전에는 텍스트보기를 편집하거나 선택할 수 없었지만 Swift 4.2에 대한 업데이트 된 답변에서는 선택 여부에 관계없이 정상적으로 작동하는 것 같습니다.
추가 연구
이 답변은이 질문에 대한 몇 가지 다른 답변을 바탕으로 작성되었습니다. 이것들 외에도
이것은 @tarmes 답변을 바탕으로 약간 수정 된 버전입니다. value변수를 가져 와서 아무것도 null조정할 수 없었지만 아래의 조정은 없었습니다. 또한 결과 작업을 결정하기 위해 전체 특성 사전이 반환되어야했습니다. 나는 이것을 의견에 넣었을 것이지만 그렇게 할 담당자는없는 것 같습니다. 프로토콜을 위반 한 경우 사전에 사과드립니다.
특정 조정은 textView.textStorage대신에 사용하는 것입니다 textView.attributedText. 아직도 배우고있는 iOS 프로그래머로서 이것이 왜 그런지 잘 모르겠지만 다른 누군가가 우리를 깨우칠 수도 있습니다.
탭 처리 방법의 특정 수정 사항 :
NSDictionary *attributesOfTappedText = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range];
뷰 컨트롤러의 전체 코드
- (void)viewDidLoad
{
[super viewDidLoad];
self.textView.attributedText = [self attributedTextViewString];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textTapped:)];
[self.textView addGestureRecognizer:tap];
}
- (NSAttributedString *)attributedTextViewString
{
NSMutableAttributedString *paragraph = [[NSMutableAttributedString alloc] initWithString:@"This is a string with " attributes:@{NSForegroundColorAttributeName:[UIColor blueColor]}];
NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a tappable string"
attributes:@{@"tappable":@(YES),
@"networkCallRequired": @(YES),
@"loadCatPicture": @(NO)}];
NSAttributedString* anotherAttributedString = [[NSAttributedString alloc] initWithString:@" and another tappable string"
attributes:@{@"tappable":@(YES),
@"networkCallRequired": @(NO),
@"loadCatPicture": @(YES)}];
[paragraph appendAttributedString:attributedString];
[paragraph appendAttributedString:anotherAttributedString];
return [paragraph copy];
}
- (void)textTapped:(UITapGestureRecognizer *)recognizer
{
UITextView *textView = (UITextView *)recognizer.view;
// Location of the tap in text-container coordinates
NSLayoutManager *layoutManager = textView.layoutManager;
CGPoint location = [recognizer locationInView:textView];
location.x -= textView.textContainerInset.left;
location.y -= textView.textContainerInset.top;
NSLog(@"location: %@", NSStringFromCGPoint(location));
// Find the character that's been tapped on
NSUInteger characterIndex;
characterIndex = [layoutManager characterIndexForPoint:location
inTextContainer:textView.textContainer
fractionOfDistanceBetweenInsertionPoints:NULL];
if (characterIndex < textView.textStorage.length) {
NSRange range;
NSDictionary *attributes = [textView.textStorage attributesAtIndex:characterIndex effectiveRange:&range];
NSLog(@"%@, %@", attributes, NSStringFromRange(range));
//Based on the attributes, do something
///if ([attributes objectForKey:...)] //make a network call, load a cat Pic, etc
}
}
iOS 7에서는 커스텀 링크를 만들고 탭에서 원하는 것을하는 것이 훨씬 쉬워졌습니다. Ray Wenderlich 에는 좋은 예가 있습니다.
NSLayoutManager *layoutManager = textView.layoutManager;
CGPoint location = [touch locationInView:textView];
NSUInteger characterIndex;
characterIndex = [layoutManager characterIndexForPoint:location
inTextContainer:textView.textContainer
fractionOfDistanceBetweenInsertionPoints:NULL];
if (characterIndex < textView.textStorage.length) {
// valid index
// Find the word range here
// using -enumerateSubstringsInRange:options:usingBlock:
}
NSLinkAttributeName으로 간단히 해결할 수있었습니다.
스위프트 2
class MyClass: UIViewController, UITextViewDelegate {
@IBOutlet weak var tvBottom: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let attributedString = NSMutableAttributedString(string: "click me ok?")
attributedString.addAttribute(NSLinkAttributeName, value: "cs://moreinfo", range: NSMakeRange(0, 5))
tvBottom.attributedText = attributedString
tvBottom.delegate = self
}
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
UtilityFunctions.alert("clicked", message: "clicked")
return false
}
}
Swift 3를 사용하여 속성이 지정된 텍스트에서 동작을 탐지하는 완전한 예
let termsAndConditionsURL = TERMS_CONDITIONS_URL;
let privacyURL = PRIVACY_URL;
override func viewDidLoad() {
super.viewDidLoad()
self.txtView.delegate = self
let str = "By continuing, you accept the Terms of use and Privacy policy"
let attributedString = NSMutableAttributedString(string: str)
var foundRange = attributedString.mutableString.range(of: "Terms of use") //mention the parts of the attributed text you want to tap and get an custom action
attributedString.addAttribute(NSLinkAttributeName, value: termsAndConditionsURL, range: foundRange)
foundRange = attributedString.mutableString.range(of: "Privacy policy")
attributedString.addAttribute(NSLinkAttributeName, value: privacyURL, range: foundRange)
txtView.attributedText = attributedString
}
그런 다음 shouldInteractWith URLUITextViewDelegate 대리자 메서드를 사용 하여 작업을 잡을 수 있으므로 대리자를 올바르게 설정했는지 확인하십시오.
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "WebView") as! SKWebViewController
if (URL.absoluteString == termsAndConditionsURL) {
vc.strWebURL = TERMS_CONDITIONS_URL
self.navigationController?.pushViewController(vc, animated: true)
} else if (URL.absoluteString == privacyURL) {
vc.strWebURL = PRIVACY_URL
self.navigationController?.pushViewController(vc, animated: true)
}
return false
}
마찬가지로 요구 사항에 따라 모든 작업을 수행 할 수 있습니다.
건배!!
로 할 수 있습니다 characterIndexForPoint:inTextContainer:fractionOfDistanceBetweenInsertionPoints:. 그것은 당신이 원하는 것과는 약간 다르게 작동 할 것입니다-당신은 두드리는 캐릭터가 마법의 단어에 속하는지 테스트해야합니다 . 그러나 복잡해서는 안됩니다.
BTW WWDC 2013의 Introducing Text Kit 를 시청하는 것이 좋습니다 .
이것은 텍스트보기에서 짧은 링크, 멀티 링크로 정상적으로 작동 할 수 있습니다. iOS 6,7,8에서도 정상적으로 작동합니다.
- (void)tappedTextView:(UITapGestureRecognizer *)tapGesture {
if (tapGesture.state != UIGestureRecognizerStateEnded) {
return;
}
UITextView *textView = (UITextView *)tapGesture.view;
CGPoint tapLocation = [tapGesture locationInView:textView];
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink|NSTextCheckingTypePhoneNumber
error:nil];
NSArray* resultString = [detector matchesInString:self.txtMessage.text options:NSMatchingReportProgress range:NSMakeRange(0, [self.txtMessage.text length])];
BOOL isContainLink = resultString.count > 0;
if (isContainLink) {
for (NSTextCheckingResult* result in resultString) {
CGRect linkPosition = [self frameOfTextRange:result.range inTextView:self.txtMessage];
if(CGRectContainsPoint(linkPosition, tapLocation) == 1){
if (result.resultType == NSTextCheckingTypePhoneNumber) {
NSString *phoneNumber = [@"telprompt://" stringByAppendingString:result.phoneNumber];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNumber]];
}
else if (result.resultType == NSTextCheckingTypeLink) {
[[UIApplication sharedApplication] openURL:result.URL];
}
}
}
}
}
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView
{
UITextPosition *beginning = textView.beginningOfDocument;
UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
UITextPosition *end = [textView positionFromPosition:start offset:range.length];
UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
CGRect firstRect = [textView firstRectForRange:textRange];
CGRect newRect = [textView convertRect:firstRect fromView:textView.textInputView];
return newRect;
}
Swift 5 및 iOS 12를 사용하면 일부 TextKit 구현 으로 하위 클래스를 UITextView만들고 재정 의하여 point(inside:with:)일부만 탭 가능하게 만들 수 있습니다 NSAttributedStrings.
다음 코드는 UITextView밑줄이있는 탭에만 반응 하는을 만드는 방법을 보여줍니다 NSAttributedString.
InteractiveUnderlinedTextView.swift
import UIKit
class InteractiveUnderlinedTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
isScrollEnabled = false
isEditable = false
isSelectable = false
isUserInteractionEnabled = true
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let superBool = super.point(inside: point, with: event)
let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
guard characterIndex < textStorage.length else { return false }
let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil)
return superBool && attributes[NSAttributedString.Key.underlineStyle] != nil
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let linkTextView = InteractiveUnderlinedTextView()
linkTextView.backgroundColor = .orange
let mutableAttributedString = NSMutableAttributedString(string: "Some text\n\n")
let attributes = [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue]
let underlinedAttributedString = NSAttributedString(string: "Some other text", attributes: attributes)
mutableAttributedString.append(underlinedAttributedString)
linkTextView.attributedText = mutableAttributedString
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(underlinedTextTapped))
linkTextView.addGestureRecognizer(tapGesture)
view.addSubview(linkTextView)
linkTextView.translatesAutoresizingMaskIntoConstraints = false
linkTextView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
linkTextView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
linkTextView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor).isActive = true
}
@objc func underlinedTextTapped(_ sender: UITapGestureRecognizer) {
print("Hello")
}
}
Swift에이 확장을 사용하십시오.
import UIKit
extension UITapGestureRecognizer {
func didTapAttributedTextInTextView(textView: UITextView, inRange targetRange: NSRange) -> Bool {
let layoutManager = textView.layoutManager
let locationOfTouch = self.location(in: textView)
let index = layoutManager.characterIndex(for: locationOfTouch, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(index, targetRange)
}
}
UITapGestureRecognizer다음 선택기로 텍스트보기에 추가하십시오 .
guard let text = textView.attributedText?.string else {
return
}
let textToTap = "Tap me"
if let range = text.range(of: tapableText),
tapGesture.didTapAttributedTextInTextView(textView: textTextView, inRange: NSRange(range, in: text)) {
// Tap recognized
}
'Programing' 카테고리의 다른 글
| 이 쿼리로 페이징 (건너 뛰기 / 취소) 기능 구현 (0) | 2020.07.23 |
|---|---|
| Visual Studio 디버그 모드에서 개체 값 복사 (0) | 2020.07.23 |
| 예외 처리기에서 오류가 발생했습니다. (0) | 2020.07.23 |
| CSS를 사용하여 숫자 (소수점, 천 단위 구분 기호 등) 서식 지정 (0) | 2020.07.22 |
| Android에서 쿠키를 사용하여 http 요청을하려면 어떻게해야합니까? (0) | 2020.07.22 |
