You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS中实现带超链接的可本地化字符串方案咨询

Solutions for Localizable Clickable "Terms of Service" Text in iOS

Hey Andrew, I’ve dealt with exactly this problem before—combining localization with clickable links in iOS can be tricky if you’re relying on basic labels, but there are a few solid approaches that get the job done. Let’s break them down:

Option 1: UILabel + NSAttributedString + Tap Gesture

This gives you full control over styling while supporting localization. The key is to use localized strings for both the full message and the link text, then dynamically find the link’s range in the localized string.

Step-by-Step Implementation

  1. Add Localization Strings
    In your Localizable.strings files (one for each language):

    • English:
      "tos_agreement" = "By using our services, you agree to our terms of service";
      "tos_link_text" = "terms of service";
      
    • Japanese/German: Add the translated versions of both strings.
  2. Create Attributed Text & Add Tap Handling

    import UIKit
    
    class YourViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            setupTOSLabel()
        }
    
        private func setupTOSLabel() {
            // Fetch localized text
            let agreementText = NSLocalizedString("tos_agreement", comment: "TOS agreement message")
            let linkText = NSLocalizedString("tos_link_text", comment: "TOS link text")
    
            // Build attributed string
            let attributedString = NSMutableAttributedString(string: agreementText)
            guard let linkRange = agreementText.range(of: linkText) else { return }
            let nsRange = NSRange(linkRange, in: agreementText)
    
            // Style the link
            attributedString.addAttribute(.foregroundColor, value: UIColor.systemBlue, range: nsRange)
            attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: nsRange)
            attributedString.addAttribute(.link, value: "https://your-tos-url.com", range: nsRange)
    
            // Configure label
            let tosLabel = UILabel()
            tosLabel.attributedText = attributedString
            tosLabel.isUserInteractionEnabled = true
            tosLabel.numberOfLines = 0
            tosLabel.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(tosLabel)
    
            // Add constraints (adjust as needed)
            NSLayoutConstraint.activate([
                tosLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                tosLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                tosLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
                tosLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
            ])
    
            // Add tap gesture
            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTOSTap(_:)))
            tosLabel.addGestureRecognizer(tapGesture)
        }
    
        @objc private func handleTOSTap(_ gesture: UITapGestureRecognizer) {
            guard let label = gesture.view as? UILabel, let attributedText = label.attributedText else { return }
            let tapLocation = gesture.location(in: label)
    
            // Calculate tapped character index
            let layoutManager = NSLayoutManager()
            let textContainer = NSTextContainer(size: label.bounds.size)
            textContainer.lineFragmentPadding = 0
            textContainer.maximumNumberOfLines = label.numberOfLines
            textContainer.lineBreakMode = label.lineBreakMode
            layoutManager.addTextContainer(textContainer)
    
            let textStorage = NSTextStorage(attributedString: attributedText)
            textStorage.addLayoutManager(layoutManager)
    
            let characterIndex = layoutManager.characterIndex(for: tapLocation, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    
            // Check if tap is on the link
            if characterIndex < textStorage.length {
                let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil)
                if let urlString = attributes[.link] as? String, let url = URL(string: urlString) {
                    // Open your WebViewController
                    let webVC = WebViewController(url: url)
                    present(webVC, animated: true)
                }
            }
        }
    }
    

UITextView natively supports clickable links and works great with localization if you use HTML-formatted localized strings.

Implementation

  1. Add HTML Localization String
    In Localizable.strings:

    • English:
      "tos_agreement_html" = "By using our services, you agree to our <a href='https://your-tos-url.com'>terms of service</a>";
      
    • Translate the HTML string for Japanese/German (keep the <a> tag intact, just change the text).
  2. Configure UITextView

    import UIKit
    
    class YourViewController: UIViewController, UITextViewDelegate {
        override func viewDidLoad() {
            super.viewDidLoad()
            setupTOSTextView()
        }
    
        private func setupTOSTextView() {
            let htmlText = NSLocalizedString("tos_agreement_html", comment: "TOS agreement with HTML link")
            
            // Convert HTML to attributed string
            guard let data = htmlText.data(using: .utf8),
                  let attributedString = try? NSAttributedString(data: data, options: [
                      .documentType: NSAttributedString.DocumentType.html,
                      .characterEncoding: String.Encoding.utf8.rawValue
                  ], documentAttributes: nil) else { return }
    
            // Configure text view
            let tosTextView = UITextView()
            tosTextView.attributedText = attributedString
            tosTextView.isEditable = false
            tosTextView.isSelectable = true
            tosTextView.dataDetectorTypes = .link
            tosTextView.delegate = self
            tosTextView.backgroundColor = .clear
            tosTextView.textContainerInset = .zero
            tosTextView.textContainer.lineFragmentPadding = 0
            tosTextView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(tosTextView)
    
            // Add constraints
            NSLayoutConstraint.activate([
                tosTextView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                tosTextView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                tosTextView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
                tosTextView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
            ])
        }
    
        // Handle link tap to open WebView instead of system browser
        func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
            let webVC = WebViewController(url: URL)
            present(webVC, animated: true)
            return false // Prevent default system browser open
        }
    }
    

Option 3: Fixing ActiveLabel for Localization

If you prefer sticking with ActiveLabel, the issue is likely that you’re hardcoding the English link text for matching. Instead, use the localized link text to set up the custom pattern:

import ActiveLabel

class YourViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        setupActiveLabel()
    }

    private func setupActiveLabel() {
        let activeLabel = ActiveLabel()
        let agreementText = NSLocalizedString("tos_agreement", comment: "")
        let linkText = NSLocalizedString("tos_link_text", comment: "")

        activeLabel.text = agreementText
        activeLabel.numberOfLines = 0
        activeLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(activeLabel)

        // Configure custom tap for localized link text
        let tosLinkType = ActiveType.custom(pattern: linkText)
        activeLabel.customColor[tosLinkType] = .systemBlue
        activeLabel.customSelectedColor[tosLinkType] = .systemBlue.withAlphaComponent(0.7)
        activeLabel.handleCustomTap(for: tosLinkType) { _ in
            let webVC = WebViewController(url: URL(string: "https://your-tos-url.com")!)
            present(webVC, animated: true)
        }

        // Add constraints
        NSLayoutConstraint.activate([
            activeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            activeLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            activeLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            activeLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
}

Final Notes

  • Localization Best Practice: Always use separate string keys for dynamic parts (like the link text) instead of hardcoding substrings—this ensures your app works seamlessly across all supported languages.
  • WebView Setup: Make sure your WebViewController properly loads the provided URL and handles navigation as needed.

内容的提问来源于stack exchange,提问作者Andrew Ng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:20:41