画像エディターのテキスト入力時のラベルについて
引っ張ると回転しながらサイズも変えられるラベル。よくあるらしい。
初めて見たときは、これどうやって実装するんだ??と思ったけど、ラベルの中心が変わらないことに気づけば 適当に実装しても似た感じのができた。
デモ
ViewDidLoadに下記を貼ると
let label = TextToolLabel() label.bind(to: view) label.text = "アイウエオ"
実装について
View自体はこんな感じに配置した。
メインの右下の丸は
中心を基準に回転と拡大縮小をする。フォントサイズが一定より小さいと、回転だけが行われる。
フォントサイズを更新 -> それにあうViewのサイズを決めるのか
Viewのサイズを変更 -> それに合うフォントのサイズを決定するのか
について悩んだ。
全体
class TextToolLabel: UIView { var text: String? { get { return label.text } set { label.text = newValue adjustSizeToFitFont() } } override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .clear addSubview(label) addSubview(editView) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private lazy var label: UILabel = { let l = UILabel() l.textColor = .black l.textAlignment = .center l.layer.borderColor = UIColor.gray.cgColor l.layer.borderWidth = 2 l.isUserInteractionEnabled = true l.addGestureRecognizer(movePanGesture) return l }() private let circleSize: CGFloat = 30 private lazy var editView: UIView = { let v = UIView() v.backgroundColor = .cyan v.layer.cornerRadius = circleSize / 2 v.addGestureRecognizer(editPanGesture) //右下にぴったりくっつくように v.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin] v.frame.origin = CGPoint(x: bounds.maxX-circleSize, y: bounds.maxY-circleSize) v.frame.size = .init(width: circleSize, height: circleSize) return v }() func bind(to: UIView) { to.addSubview(self) center = to.center } private lazy var movePanGesture: UIPanGestureRecognizer = { return UIPanGestureRecognizer(target: self, action: #selector(actionMove)) }() private var previousLocation: CGPoint = .zero private var initialCenter: CGPoint = .zero //Labelに追加。ラベルの位置を移動する。 @objc private func actionMove(_ sender: UIPanGestureRecognizer) { let touchLocation = sender.location(in: superview) if sender.state == .began { previousLocation = touchLocation initialCenter = center } else { let dx = touchLocation.x - previousLocation.x let dy = touchLocation.y - previousLocation.y center = CGPoint(x: initialCenter.x + dx, y: initialCenter.y + dy) } } private lazy var editPanGesture: UIPanGestureRecognizer = { return UIPanGestureRecognizer(target: self, action: #selector(actionEdit)) }() private let minimumFontSize: CGFloat = 10 private var initialDistance: CGFloat = 0 private var initialiFontSize: CGFloat = 0 //右下の丸いViewに追加する。引っ張って拡大縮小と回転する。 @objc private func c(_ sender: UIPanGestureRecognizer) { let touchLocation = sender.location(in: superview) switch sender.state { case .began: initialDistance = CGPoint.distance(from: center, to: touchLocation) initialiFontSize = label.fontSize default: /*回転する処理*/ //viewの中心とタップした座標の正弦を取得する let angle = atan2(touchLocation.y - center.y, touchLocation.x - center.x) //現在の適応されている角度 let dif = transform.rotateAngle //差分だけ回転する transform = transform.rotated(by: angle-dif) /*文字を大きくする(Viewを拡大する)処理*/ let distance = CGPoint.distance(from: center, to: touchLocation) let scale = Float(distance / initialDistance) //フォントサイズを大きくする -> それに合うようにViewのSizeも大きくする。 let size = initialiFontSize*CGFloat(scale) label.updateFontsize(to: max(size, minimumFontSize)) adjustSizeToFitFont() } } //テキストが増えるたびにフォントは変えずにViewのサイズを変更する。 private func adjustSizeToFitFont() { //現在のフォントサイズで、テキストをぴったり表示するのに必要なサイズを計算する。 let attributedText = NSAttributedString(string: label.text!, attributes: [.font: label.font!]) let greatest = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) let rect = attributedText.boundingRect(with: greatest, options: .usesLineFragmentOrigin, context: nil) label.bounds.size = rect.size bounds.size = CGSize(width: label.bounds.size.width + circleSize, height: label.bounds.size.height + circleSize) label.center = bounds.center } } fileprivate extension CGPoint { static func distance(from: CGPoint, to: CGPoint) -> CGFloat { let dx = from.x - to.x let dy = from.y - to.y return sqrt(dx * dx + dy * dy) } } fileprivate extension CGRect { var center: CGPoint { return CGPoint(x: midX, y: midY) } } fileprivate extension CGAffineTransform { //radian var rotateAngle: CGFloat { return atan2(b, a) } } fileprivate extension UILabel { var fontSize: CGFloat { return font.pointSize } func updateFontsize(to: CGFloat) { font = UIFont(name: font.fontName, size: to) } }