我使用 UIBezierPath 来绘制圆、线、自由线等对象...... 我有一个功能,我必须从“func drawPoints(bounds:CGRect)”中的 topRight 和 BottomRight 变量调整圆形和直线对象的大小。
import UIKit
enum ShapeType {
case circle, rectangle, line, pencil
}
class DrawingView: UIView {
private var path = UIBezierPath()
private var startPoint: CGPoint?
private var shapeType: ShapeType?
private var strokeColor = #colorLiteral(red: 0.2274509804, green: 0.7098039216, blue: 0.2862745098, alpha: 1)
private var lineWidth: CGFloat = 3.0
private var paths: [(path: UIBezierPath, color: UIColor)] = []
private var selectedPathIndex: Int?
private var offset = CGPoint.zero
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
backgroundColor = .clear
isMultipleTouchEnabled = false
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
startPoint = touch.location(in: self)
print("selectPath ==> \(point)")
selectedPathIndex = nil
setNeedsDisplay()
for (index, path) in paths.enumerated() {
if path.path.bounds.contains(startPoint ?? CGPoint()){
selectedPathIndex = index
self.path = path.path
print(offset)
offset = CGPoint(x: (startPoint?.x ?? 0.0) - path.path.bounds.origin.x, y: (startPoint?.y ?? 0) - path.path.bounds.origin.y)
print(offset)
print("did touch index", selectedPathIndex)
setNeedsDisplay()
return
}
}
path = UIBezierPath()
if shapeType == .pencil {
path.move(to: startPoint!)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, let shapeType = shapeType else { return }
let currentPoint = touch.location(in: self)
print(currentPoint)
if let index = selectedPathIndex {
let newOrigin = CGPoint(x: currentPoint.x - offset.x, y: currentPoint.y - offset.y)
print(newOrigin)
let translation = CGAffineTransform(translationX: newOrigin.x - path.bounds.origin.x, y: newOrigin.y - path.bounds.origin.y)
print(translation)
self.paths[index].path.apply(translation)
setNeedsDisplay()
}else{
if shapeType == .pencil {
if selectedPathIndex != nil{
path.move(to: currentPoint)
}else{
path.addLine(to: currentPoint)
}
setNeedsDisplay()
} else {
path.removeAllPoints()
guard let startPoint = startPoint else { return }
switch shapeType {
case .line:
path.move(to: startPoint)
path.addLine(to: currentPoint)
case .rectangle:
path = UIBezierPath(rect: CGRect(origin: startPoint, size: CGSize(width: currentPoint.x - startPoint.x, height: currentPoint.y - startPoint.y)))
case .circle:
let radius = hypot(currentPoint.x - startPoint.x, currentPoint.y - startPoint.y)
path = UIBezierPath(arcCenter: startPoint, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
case .pencil:
break
}
setNeedsDisplay()
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let shapeType = shapeType else { return }
if self.selectedPathIndex == nil{
if shapeType != .pencil {
paths.append((path.copy() as! UIBezierPath, strokeColor))
} else {
paths.append((path, strokeColor))
}
}
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
for pathData in paths {
pathData.color.setStroke()
pathData.path.lineWidth = lineWidth
pathData.path.stroke()
}
strokeColor.setStroke()
path.lineWidth = lineWidth
path.stroke()
if selectedPathIndex != nil{
for (index, pathData) in paths.enumerated() {
pathData.color.setStroke()
pathData.path.lineWidth = lineWidth
pathData.path.stroke()
if let selectedIndex = selectedPathIndex, selectedIndex == index {
let bounds = path.bounds
// Draw a rectangle around the selected path
if isPencil(path: path) {
print("This path is a line.")
self.drawSquare(bounds: bounds)
} else if isCurve(path: path) {
print("This path is a curve.")
self.drawSquare(bounds: bounds)
self.drawPoints(bounds: bounds)
} else if isLine(path: path){
self.drawPoints(bounds: bounds)
}
}
}
}
}
func drawSquare(bounds:CGRect){
let selectionRect = UIBezierPath(rect: bounds) // Create a rectangle slightly larger than the path
#colorLiteral(red: 0.1490196078, green: 0.5019607843, blue: 0.9215686275, alpha: 1).setStroke()
selectionRect.lineWidth = 1 // Set the line width for the rectangle
selectionRect.stroke() // Draw the rectangle
}
func drawPoints(bounds:CGRect){
let topRight = CGPoint(x: bounds.maxX, y: bounds.minY)
let bottomRight = CGPoint(x: bounds.minX, y: bounds.maxY)
self.drawPoint(at: topRight, color: #colorLiteral(red: 0.1490196078, green: 0.5019607843, blue: 0.9215686275, alpha: 1)) // Change color and size as needed
self.drawPoint(at: bottomRight, color: #colorLiteral(red: 0.1490196078, green: 0.5019607843, blue: 0.9215686275, alpha: 1))
}
func drawPoint(at point: CGPoint, color: UIColor) {
let pointSize: CGFloat = 16
let pointRect = CGRect(x: point.x - pointSize / 2, y: point.y - pointSize / 2, width: pointSize, height: pointSize)
let pointPath = UIBezierPath(ovalIn: pointRect)
color.setFill()
UIColor.white.setStroke()
pointPath.lineWidth = 5
pointPath.stroke()
color.setStroke()
pointPath.fill()
}
func clear() {
path.removeAllPoints()
paths.removeAll()
setNeedsDisplay()
}
func setStrokeColor(_ color: UIColor) {
strokeColor = color
}
func setLineWidth(_ width: CGFloat) {
lineWidth = width
}
func setShapeType(_ shape: ShapeType) {
shapeType = shape
}
func undo() {
if !paths.isEmpty {
path.removeAllPoints()
paths.removeLast()
self.setNeedsDisplay()
}
}
func isLine(path: UIBezierPath) -> Bool {
var isLine = true
path.cgPath.applyWithBlock { element in
let type = element.pointee.type
if type == .addCurveToPoint || type == .addQuadCurveToPoint {
isLine = false
}
}
return isLine
}
func isPencil(path: UIBezierPath) -> Bool {
var isLine = true
var isLineCount = 0
path.cgPath.applyWithBlock { element in
let type = element.pointee.type
isLineCount += 1
if type == .addCurveToPoint || type == .addQuadCurveToPoint {
isLine = false
}
}
return isLine && isLineCount > 2
}
func isCurve(path: UIBezierPath) -> Bool {
var isCurve = false
path.cgPath.applyWithBlock { element in
let type = element.pointee.type
if type == .addCurveToPoint || type == .addQuadCurveToPoint {
isCurve = true
}
}
return isCurve
}
}
我已经实现了这一点:https://drive.google.com/file/d/1ZipL-fPNS2y-X-BWr5SgKYIRr1o0RXUg/view?usp=sharing
我想实现这样的目标: https://drive.google.com/file/d/1aLAPbpjDzYPI5mCF2IQ00RJ02NU00YGc/view?usp=sharing
如何实现此功能?过去两天我一直在寻找解决方案,但没有找到与我的功能相关的任何内容。任何类型的指导将不胜感激。
谢谢!
我们可以使用
CGAffineTransform
来移动和缩放路径。
这是一个扩展示例:
extension UIBezierPath {
func pathIn(targetRect: CGRect) -> UIBezierPath? {
// get a copy of the path
guard let newPath = copy() as? UIBezierPath else { return nil }
// get current bounding rect
let origRect = newPath.bounds
// we want to translate (move) the path to 0,0
let zeroTR = CGAffineTransform(translationX: -origRect.origin.x, y: -origRect.origin.y)
newPath.apply(zeroTR)
// now transform the path to new x,y and width,height
let tr = CGAffineTransform(translationX: targetRect.origin.x, y: targetRect.origin.y)
.scaledBy(x: targetRect.size.width / origRect.width, y: targetRect.size.height / origRect.height)
newPath.apply(tr)
return newPath
}
}
因此,如果我们有一条路径,其边界框为
(40.0, 40.0, 90.0, 60.0)
,并且我们想将其缩放到 180 x 120
的大小,我们可以这样做:
let targetRect: CGRect = .init(x: 40.0, y: 40.0, width: 180.0, height: 120.0)
let transformedPath = originalPath.pathIn(targetRect: targetRect)
示例:
这里有一些示例代码来创建它......
绘图视图 - 在原始状态下绘制带有虚线边界框的蓝色路径,并在
targetRect
中绘制红色变换路径(带有虚线边界框)
class SampleDrawView: UIView {
public var currentPath: UIBezierPath! { didSet { setNeedsDisplay() } }
public var targetRect: CGRect = .zero { didSet { setNeedsDisplay() } }
override func draw(_ rect: CGRect) {
// don't try to draw if we don't have a path
guard currentPath != nil else { return }
// let's draw a dashed-outline rect, 2-points larger than the original path's bounding box
UIColor.systemBlue.setStroke()
let boxPath = UIBezierPath(rect: currentPath.bounds.insetBy(dx: -2.0, dy: -2.0))
boxPath.setLineDash([8, 8], count: 1, phase: 0.0)
boxPath.stroke()
// draw the original path in blue
UIColor.blue.setStroke()
currentPath.lineWidth = 3
currentPath.stroke()
// make sure targetRect has been set
guard targetRect != .zero else { return }
// make sure we get a valid transformed path that fits in the target rectangle
guard let transformedPath = currentPath.pathIn(targetRect: targetRect) else { return }
// let's draw a dashed-outline rect, 2-points larger than the target rect
UIColor.lightGray.setStroke()
let targetBoxPath = UIBezierPath(rect: targetRect.insetBy(dx: -2.0, dy: -2.0))
targetBoxPath.setLineDash([8, 8], count: 1, phase: 0.0)
targetBoxPath.stroke()
// now we draw the transformed path in red
UIColor.red.setStroke()
transformedPath.lineWidth = 3
transformedPath.stroke()
}
}
示例控制器 - 点击任意位置即可循环浏览各种目标矩形
class SamplePathVC: UIViewController {
var samplePath: UIBezierPath!
var someRects: [CGRect] = []
let exampleView = SampleDrawView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
exampleView.backgroundColor = UIColor(white: 0.975, alpha: 1.0)
exampleView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(exampleView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
exampleView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
exampleView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
exampleView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
exampleView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
samplePath = UIBezierPath()
samplePath.move(to: .init(x: 40.0, y: 40.0))
samplePath.addLine(to: .init(x: 100.0, y: 40.0))
samplePath.addQuadCurve(to: .init(x: 100.0, y: 100.0), controlPoint: .init(x: 160.0, y: 70.0))
samplePath.addLine(to: .init(x: 80.0, y: 80.0))
samplePath.addLine(to: .init(x: 60.0, y: 100.0))
samplePath.addLine(to: .init(x: 40.0, y: 80.0))
exampleView.currentPath = samplePath
let x: CGFloat = samplePath.bounds.origin.x
let y: CGFloat = samplePath.bounds.origin.y
let w: CGFloat = samplePath.bounds.size.width
let h: CGFloat = samplePath.bounds.size.height
someRects = [
// scale by 2x
.init(x: x, y: y, width: w * 2.0, height: h * 2.0),
// scale by 3x
.init(x: x, y: y, width: w * 3.0, height: h * 3.0),
// move but no scale
.init(x: x + 100.0, y: y + 100.0, width: w, height: h),
// the rest are move AND scale
.init(x: 40.0, y: 120.0, width: 120.0, height: 120.0),
.init(x: 40.0, y: 120.0, width: 240.0, height: 240.0),
.init(x: 40.0, y: 120.0, width: 240.0, height: 500.0),
.init(x: 200.0, y: 60.0, width: 60.0, height: 560.0),
.init(x: 20.0, y: 300.0, width: 280.0, height: 80.0),
]
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// cycle through the sample target rects on each tap
let r = someRects.removeFirst()
someRects.append(r)
exampleView.targetRect = r
}
}