つくったもの
SwitUIのTextをドラッグでぐりぐりと操作できるようにしてみました。
できること
- テキストの回転、サイズ変更
- テキストの色の変更
- テキストのドラッグ移動
使用方法
FancyText(text: "Sample Text")
// フォントサイズと色を指定する場合
let fontSize = UIFont.preferredFont(forTextStyle: .largeTitle).pointSize
FancyText(text: "Sample Text", fontSize: fontSize, color: Color.primary)
実装ポイント
標準のTextをそのまま使用し、overlayを使って動作を拡張しました。DragGestureでドラッグ操作を捕捉し、rotationEffectで回転を反映させます。
回転角度の求め方
仮想的な外周円を設定し、DragGestureで通知される移動座標から、ピタゴラスの定理と三角関数を順番に適用し、Textの角度と大きさを求めていきます。
(実装コードの抜粋)
Image(systemName: "arrow.counterclockwise")
.padding(4)
.clipShape(Circle())
.position(handlePosition)
.gesture(
DragGesture()
.onChanged { value in
// 中心点からハンドルとの距離(外周円の半径)
let dx = value.location.x - center.x
let dy = value.location.y - center.y
r = sqrt(pow(dx, 2) + pow(dy, 2))
// ハンドル位置とのオフセット角度
offsetAngle = atan2(rectSize.height, rectSize.width)
// 長方形の回転角度(ハンドルが右下角にあるためその分補正する)
rotationAngle = atan2(dy, dx) - offsetAngle
// 外周円に内接する長方形の辺の長さ
// https://keisan.casio.jp/exec/system/1161228786
let newWidth = r * 2 * sin(centralAngle1 / 2)
let newHeight = r * 2 * sin(centralAngle2 / 2)
// 位置とサイズを更新
DispatchQueue.main.async {
handlePosition = CGPoint(x: center.x + dx, y: center.y + dy)
rectSize = CGSize(width: newWidth, height: newHeight)
}
}
)
再タップ時のハンドル座標の求め方
Textをもう一度タップした場合はハンドルを再表示するために、現在の回転角度からハンドルの座標を求める必要があります。回転行列の公式を利用して、ハンドル(長方形)の4点の座標を求めます。
func rotatedRectangleVertices(center: CGPoint, size: CGSize, angle: CGFloat) -> [CGPoint] {
let halfWidth = size.width / 2.0
let halfHeight = size.height / 2.0
// 長方形の4つの頂点(中心を原点として)
let points = [
CGPoint(x: -halfWidth, y: -halfHeight),
CGPoint(x: halfWidth, y: -halfHeight),
CGPoint(x: -halfWidth, y: halfHeight),
CGPoint(x: halfWidth, y: halfHeight)
]
return points.map { point in
// 回転行列を使用して各点を回転させる
let x = center.x + (point.x * cos(angle) - point.y * sin(angle))
let y = center.y + (point.x * sin(angle) + point.y * cos(angle))
return CGPoint(x: x, y: y)
}
}
サンプルコード