【iOS】リンク先に遷移可能なチェックボックスを作ってみる
完成品
Webだと稀にある、テキストまでクリック対象のチェックボックスかつ、リンクだけ別のアクション(別ページを開くとか)をiOSで実装してみた。
作って思ったけど、普通にチェックボックスだけタップ可で良い気がするわ...
ロジック
こちらにあるように、UILabelの特定の文字が押された判定はできる。 また、UIButtonのラベルはUILabelなので、この論理で実装できる。
作り方
Xcode: 10.1
UITapGestureRecognizerを拡張
元ネタだとちょっと古いので変更する
extension UITapGestureRecognizer { func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool { // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: CGSize.zero) let textStorage = NSTextStorage(attributedString: label.attributedText!) // Configure layoutManager and textStorage layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) // Configure textContainer textContainer.lineFragmentPadding = 0.0 textContainer.lineBreakMode = label.lineBreakMode textContainer.maximumNumberOfLines = label.numberOfLines let labelSize = label.bounds.size textContainer.size = labelSize // Find the tapped character location and compare it to the specified range let locationOfTouchInLabel = self.location(in: label) let textBoundingBox = layoutManager.usedRect(for: textContainer) let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y) let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y); let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) return NSLocationInRange(indexOfCharacter, targetRange) } }
UIButtonにイベント等を設定
import UIKit class ViewController: UIViewController { @IBOutlet weak var button: UIButton! private var clickableRange: NSRange! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. setUpButton() } @objc func onClickButton(_ button: UIButton) { button.isSelected = !button.isSelected } @objc func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) { if gestureRecognizer.didTapAttributedTextInLabel(label: button.titleLabel!, inRange: clickableRange) { // 便器上アラートを表示している showAlert() } else { onClickButton(button) } } private func setUpButton() { // create attributed string let str = "利用規約を読みなんたからんたら" clickableRange = NSString(string: str).range(of: "利用規約") let attr = NSMutableAttributedString( string: str, attributes: [ .font: UIFont(name: "HiraginoSans-W3", size: 16)!, .foregroundColor: UIColor.black ] ) attr.addAttribute(.foregroundColor, value: UIColor.green, range: clickableRange) // set attributed string button.setAttributedTitle(attr, for: .normal) // add checkbox images button.setImage(UIImage.init(named: "off"), for: .normal) button.setImage(UIImage.init(named: "on"), for: .selected) button.titleEdgeInsets.left = 6 button.addTarget(self, action: #selector(onClickButton(_:)), for: .touchUpInside) // create gesture let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapGesture(_:))) button.titleLabel?.addGestureRecognizer(tapGesture) button.titleLabel?.isUserInteractionEnabled = true } private func showAlert() { let alert = UIAlertController(title: "リンクを", message: "開く", preferredStyle: .alert) let cancel = UIAlertAction(title: "閉じる", style: .cancel) alert.addAction(cancel) self.present(alert, animated: true) } }