Skip to content

Instantly share code, notes, and snippets.

@takehilo
Created March 28, 2019 02:54
Show Gist options
  • Save takehilo/c9abbedc5adb7dfda638a581beb4823d to your computer and use it in GitHub Desktop.
Save takehilo/c9abbedc5adb7dfda638a581beb4823d to your computer and use it in GitHub Desktop.
Resizable view implementation like rectangles in the markup feature of iOS Photo app
import UIKit
class ResizableRectangle: UIView {
private let borderWidth: CGFloat = 10
private var halfOfBorderWidth: CGFloat { return borderWidth / 2 }
private let topControl = Control(position: .top)
private let upperRightControl = Control(position: .upperRight)
private let rightControl = Control(position: .right)
private let lowerRightControl = Control(position: .lowerRight)
private let bottomControl = Control(position: .bottom)
private let lowerLeftControl = Control(position: .lowerLeft)
private let leftControl = Control(position: .left)
private let upperLeftControl = Control(position: .upperLeft)
private let borderView = UIView()
private var frameAtTouchesBegan: CGRect?
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
borderView.backgroundColor = .black
addSubview(borderView)
addSubview(topControl)
addSubview(upperRightControl)
addSubview(rightControl)
addSubview(lowerRightControl)
addSubview(bottomControl)
addSubview(lowerLeftControl)
addSubview(leftControl)
addSubview(upperLeftControl)
relocateControls()
}
private func relocateControls() {
topControl.center = CGPoint(x: frame.width / 2, y: halfOfBorderWidth)
upperRightControl.center = CGPoint(x: frame.width - halfOfBorderWidth, y: halfOfBorderWidth)
rightControl.center = CGPoint(x: frame.width - halfOfBorderWidth, y: frame.height / 2)
lowerRightControl.center = CGPoint(x: frame.width - halfOfBorderWidth, y: frame.height - halfOfBorderWidth)
bottomControl.center = CGPoint(x: frame.width / 2, y: frame.height - halfOfBorderWidth)
lowerLeftControl.center = CGPoint(x: halfOfBorderWidth, y: frame.height - halfOfBorderWidth)
leftControl.center = CGPoint(x: halfOfBorderWidth, y: frame.height / 2)
upperLeftControl.center = CGPoint(x: halfOfBorderWidth, y: halfOfBorderWidth)
}
override func layoutSubviews() {
super.layoutSubviews()
borderView.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
maskCenter()
relocateControls()
}
private func maskCenter() {
let path = UIBezierPath()
path.move(to: CGPoint(x: halfOfBorderWidth, y: halfOfBorderWidth))
path.addLine(to: CGPoint(x: frame.width - halfOfBorderWidth, y: halfOfBorderWidth))
path.addLine(to: CGPoint(x: frame.width - halfOfBorderWidth, y: frame.height - halfOfBorderWidth))
path.addLine(to: CGPoint(x: halfOfBorderWidth, y: frame.height - halfOfBorderWidth))
path.close()
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.lineWidth = borderWidth
maskLayer.fillColor = nil
borderView.layer.mask = maskLayer
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first,
touch.view != nil else { return }
frameAtTouchesBegan = frame
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first,
let view = touch.view else { return }
if let control = view as? Control {
let location = touch.location(in: superview)
resize(control: control, locationInParentCoordinate: location)
} else if view == borderView {
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
move(currentLocation: location, previousLocation: previousLocation)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
frameAtTouchesBegan = nil
}
private func resize(control: Control, locationInParentCoordinate: CGPoint) {
guard let frameAtTouchesBegan = frameAtTouchesBegan else { return }
let isLocationUpperSide = locationInParentCoordinate.y < frameAtTouchesBegan.origin.y + frameAtTouchesBegan.height
let isLocationLeftSide = locationInParentCoordinate.x < frameAtTouchesBegan.origin.x + frameAtTouchesBegan.width
switch control.position {
case .top:
frame = CGRect(
x: frameAtTouchesBegan.origin.x,
y: isLocationUpperSide ? locationInParentCoordinate.y : frameAtTouchesBegan.origin.y + frameAtTouchesBegan.height,
width: frameAtTouchesBegan.size.width,
height: isLocationUpperSide
? frameAtTouchesBegan.origin.y + frameAtTouchesBegan.height - locationInParentCoordinate.y
: locationInParentCoordinate.y - frameAtTouchesBegan.origin.y - frameAtTouchesBegan.height)
case .bottom:
frame = CGRect(
x: frameAtTouchesBegan.origin.x,
y: !isLocationUpperSide ? frameAtTouchesBegan.origin.y : locationInParentCoordinate.y,
width: frameAtTouchesBegan.size.width,
height: !isLocationUpperSide
? locationInParentCoordinate.y - frameAtTouchesBegan.origin.y
: frameAtTouchesBegan.origin.y - locationInParentCoordinate.y)
case .right:
frame = CGRect(
x: !isLocationLeftSide ? frameAtTouchesBegan.origin.x : locationInParentCoordinate.x,
y: frameAtTouchesBegan.origin.y,
width: !isLocationLeftSide
? locationInParentCoordinate.x - frameAtTouchesBegan.origin.x
: frameAtTouchesBegan.origin.x - locationInParentCoordinate.x,
height: frameAtTouchesBegan.size.height)
case .left:
frame = CGRect(
x: isLocationLeftSide ? locationInParentCoordinate.x : frameAtTouchesBegan.origin.x + frameAtTouchesBegan.width,
y: frameAtTouchesBegan.origin.y,
width: isLocationLeftSide
? frameAtTouchesBegan.origin.x + frameAtTouchesBegan.width - locationInParentCoordinate.x
: locationInParentCoordinate.x - frameAtTouchesBegan.origin.x - frameAtTouchesBegan.width,
height: frameAtTouchesBegan.size.height)
case .lowerRight:
frame = CGRect(
x: !isLocationLeftSide ? frameAtTouchesBegan.origin.x : locationInParentCoordinate.x,
y: !isLocationUpperSide ? frameAtTouchesBegan.origin.y : locationInParentCoordinate.y,
width: !isLocationLeftSide
? locationInParentCoordinate.x - frameAtTouchesBegan.origin.x
: frameAtTouchesBegan.origin.x - locationInParentCoordinate.x,
height: !isLocationUpperSide
? locationInParentCoordinate.y - frameAtTouchesBegan.origin.y
: frameAtTouchesBegan.origin.y - locationInParentCoordinate.y)
case .lowerLeft:
frame = CGRect(
x: isLocationLeftSide ? locationInParentCoordinate.x : frameAtTouchesBegan.origin.x + frameAtTouchesBegan.width,
y: !isLocationUpperSide ? frameAtTouchesBegan.origin.y : locationInParentCoordinate.y,
width: isLocationLeftSide
? frameAtTouchesBegan.origin.x + frameAtTouchesBegan.width - locationInParentCoordinate.x
: locationInParentCoordinate.x - frameAtTouchesBegan.origin.x - frameAtTouchesBegan.width,
height: !isLocationUpperSide
? locationInParentCoordinate.y - frameAtTouchesBegan.origin.y
: frameAtTouchesBegan.origin.y - locationInParentCoordinate.y)
case .upperLeft:
frame = CGRect(
x: isLocationLeftSide ? locationInParentCoordinate.x : frameAtTouchesBegan.origin.x + frameAtTouchesBegan.width,
y: isLocationUpperSide ? locationInParentCoordinate.y : frameAtTouchesBegan.origin.y + frameAtTouchesBegan.height,
width: isLocationLeftSide
? frameAtTouchesBegan.origin.x + frameAtTouchesBegan.width - locationInParentCoordinate.x
: locationInParentCoordinate.x - frameAtTouchesBegan.origin.x - frameAtTouchesBegan.width,
height: isLocationUpperSide
? frameAtTouchesBegan.origin.y + frameAtTouchesBegan.height - locationInParentCoordinate.y
: locationInParentCoordinate.y - frameAtTouchesBegan.origin.y - frameAtTouchesBegan.height)
case .upperRight:
frame = CGRect(
x: !isLocationLeftSide ? frameAtTouchesBegan.origin.x : locationInParentCoordinate.x,
y: isLocationUpperSide ? locationInParentCoordinate.y : frameAtTouchesBegan.origin.y + frameAtTouchesBegan.height,
width: !isLocationLeftSide
? locationInParentCoordinate.x - frameAtTouchesBegan.origin.x
: frameAtTouchesBegan.origin.x - locationInParentCoordinate.x,
height: isLocationUpperSide
? frameAtTouchesBegan.origin.y + frameAtTouchesBegan.height - locationInParentCoordinate.y
: locationInParentCoordinate.y - frameAtTouchesBegan.origin.y - frameAtTouchesBegan.height)
}
}
private func move(currentLocation: CGPoint, previousLocation: CGPoint) {
let distanceX = currentLocation.x - previousLocation.x
let distanceY = currentLocation.y - previousLocation.y
center = CGPoint(x: center.x + distanceX, y: center.y + distanceY)
}
}
extension ResizableRectangle {
private enum Position {
case top, upperRight, right, lowerRight, bottom, lowerLeft, left, upperLeft
}
private class Control: UIView {
private let radius: CGFloat = 8
private let borderWidth: CGFloat = 2
let position: Position
init(position: Position) {
self.position = position
super.init(frame: CGRect(x: 0, y: 0, width: radius * 2, height: radius * 2))
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
layer.borderWidth = borderWidth
layer.borderColor = UIColor.white.cgColor
layer.cornerRadius = radius
backgroundColor = .blue
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment