gpt4 book ai didi

ios - 使 UIBezierPath 可选择并更改其颜色

转载 作者:行者123 更新时间:2023-12-01 16:13:30 27 4
gpt4 key购买 nike

我有一个 UIView符合自定义 Canvas 类。这意味着用户可以在 UIView 中绘制.

每次用户完成绘制后,都需要点击添加UIButton一行将附加到 UITableView以下。

每行包含 2 个属性 name: Stringscribble: [UInt8] . scribble 属性将保存与该行关联的图形的 X 和 Y 位置。

当用户从该 UITableView 中选择任何行时然后像素的颜色将在 Canvas 上更改为相关的涂鸦。

在这里,我有一个 Android 版本的演示,我需要做类似的事情:
http://g.recordit.co/ZY21ufz5kW.gif

这是我在项目中的进展,但我坚持附加 X 和 Y 坐标的逻辑,而且我不知道如何选择涂鸦以更改 Canvas 上的颜色:

https://github.com/tygruletz/AddScribblesOnImage

这是我的Canvas类(class):

/// A class which allow the user to draw inside a UIView which will inherit this class.
class Canvas: UIView {

/// Closure to run on changes to drawing state
var isDrawingHandler: ((Bool) -> Void)?

/// The image drawn onto the canvas
var image: UIImage?

/// Caches the path for a line between touch down and touch up.
public var path = UIBezierPath()

/// An array of points that will be smoothed before conversion to a Bezier path
private var points = Array(repeating: CGPoint.zero, count: 5)

/// Keeps track of the number of points cached before transforming into a bezier
private var pointCounter = Int(0)

/// The colour to use for drawing
public var strokeColor = UIColor.orange

/// Width of drawn lines
//private var strokeWidth = CGFloat(7)

override func awakeFromNib() {

isMultipleTouchEnabled = false
path.lineWidth = 1
path.lineCapStyle = .round
}

// public function
func clear() {
image = nil
setNeedsDisplay()
}

override func draw(_ rect: CGRect) {
// Draw the cached image into the view and then draw the current path onto it
// This means the entire path is not drawn every time, just the currently smoothed section.
image?.draw(in: rect)

strokeColor.setStroke()
path.stroke()
}

private func cacheImage() {

let renderer = UIGraphicsImageRenderer(bounds: bounds)
image = renderer.image(actions: { (context) in
// Since we are not drawing a background color I've commented this out
// I've left the code in case you want to use it in the future
// if image == nil {
// // Nothing cached yet, fill the background
// let backgroundRect = UIBezierPath(rect: bounds)
// backgroundColor?.setFill()
// backgroundRect.fill()
// }

image?.draw(at: .zero)
strokeColor.setStroke()
path.stroke()
})
}
}

// UIResponder methods
extension Canvas {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first ?? UITouch()
let point = touch.location(in: self)

pointCounter = 0

points[pointCounter] = point
isDrawingHandler?(true)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first ?? UITouch()
let point = touch.location(in: self)
pointCounter += 1
points[pointCounter] = point
guard pointCounter == 4 else {
// We need 5 points to convert to a smooth Bezier Curve
return
}

// Smooth the curve
points[3] = CGPoint(x: (points[2].x + points[4].x) / 2.0, y: (points[2].y + points [4].y) / 2.0)

// Add a new bezier sub-path to the current path
path.move(to: points[0])
path.addCurve(to: points[3], controlPoint1: points[1], controlPoint2: points[2])

// Explicitly shift the points up for the new segment points for the new segment
points = [points[3], points[4], .zero, .zero, .zero]
pointCounter = 1
setNeedsDisplay()
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
cacheImage()
setNeedsDisplay()
path.removeAllPoints()
pointCounter = 0
isDrawingHandler?(false)
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
touchesEnded(touches, with: event)
}
}


这是我的 ViewController类(class):
class FirstVC: UIViewController {

// Interface Links
@IBOutlet private var canvas: Canvas! {
didSet {
canvas.isDrawingHandler = { [weak self] isDrawing in
self?.clearBtn.isEnabled = !isDrawing
}
}
}
@IBOutlet weak var mainView: UIView!
@IBOutlet weak var imageView: UIImageView!
@IBOutlet var clearBtn: UIButton!
@IBOutlet weak var itemsTableView: UITableView!
@IBOutlet weak var addScribble: UIButton!

// Properties
var itemsName: [String] = ["Rust", "Ruptured", "Chipped", "Hole", "Cracked"]
var addedItems: [DamageItem] = []

// Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(#imageLiteral(resourceName: "drawDamageOnTruck"))
itemsTableView.tableFooterView = UIView()
}

@IBAction func nextBtn(_ sender: UIBarButtonItem) {
guard
let navigationController = navigationController,
let secondVC = navigationController.storyboard?.instantiateViewController(withIdentifier: "SecondVC") as? SecondVC
else { return }

let signatureSaved = convertViewToImage(with: mainView)

secondVC.signature = signatureSaved ?? UIImage()

navigationController.pushViewController(secondVC, animated: true)
}

@IBAction func clearBtn(_ sender: UIButton) {
canvas.clear()
addedItems = []
itemsTableView.reloadData()
}

@IBAction func addScribble(_ sender: UIButton) {

let randomItem = itemsName.randomElement() ?? ""
let drawedScribbles = [UInt8]()

addedItems.append(DamageItem(name: randomItem, scribble: drawedScribbles))

itemsTableView.reloadData()
}

// Convert an UIView to UIImage
func convertViewToImage(with view: UIView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
view.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}
}

extension FirstVC: UITableViewDelegate, UITableViewDataSource {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return addedItems.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath)

cell.textLabel?.text = addedItems[indexPath.row].name

return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

print("Click on \(addedItems[indexPath.row].name)")

// Bold the selected scribble on the image.

}

/// This method is used in iOS >= 11.0 instead of `editActionsForRowAt` to Delete a row.
@available(iOS 11.0, *)
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

let actionHide = UIContextualAction(style: .destructive, title: "Delete") { action, view, handler in

self.addedItems.remove(at: indexPath.row)
self.itemsTableView.deleteRows(at: [indexPath], with: .none)
handler(true)
}
actionHide.backgroundColor = UIColor.red
return UISwipeActionsConfiguration(actions: [actionHide])
}
}

任何帮助将不胜感激!
感谢您阅读本文!

最佳答案

根本问题是您正在采用描边路径并将它们扁平化为图像。这是一个很好的优化(尽管通常我们只担心有成百上千个笔画点要渲染),但是如果它们已经渲染,那不会让您返回并重新渲染不同颜色的单个路径图像内部。

因此,解决方案是保留 CGPoint 的数组用于各种笔画/路径(​​在您的应用中称为“涂鸦”)。这些可能与已保存的 DamageItem 相关联实例,但我们想要一个用于当前手势/触摸。然后,当您选择与特定 DamageItem 关联的行时,您将丢弃保存的图像并返回并从头开始从笔划数组重新渲染,并根据需要为所选的一个着色:

class Canvas: UIView {
/// Closure to run on changes to drawing state
var isDrawingHandler: ((Bool) -> Void)?

/// The cached image drawn onto the canvas
var image: UIImage?

/// Caches the path for a line between touch down and touch up.
public var damages: [DamageItem] = [] { didSet { invalidateCachedImage() } }

/// The current scribble
public var currentScribble: [CGPoint]?

private var predictivePoints: [CGPoint]?

/// Which path is currently selected
public var selectedDamageIndex: Int? { didSet { invalidateCachedImage() } }

/// The colour to use for drawing
public var strokeColor: UIColor = .black
public var selectedStrokeColor: UIColor = .orange

/// Width of drawn lines
private var lineWidth: CGFloat = 2 { didSet { invalidateCachedImage() } }

override func awakeFromNib() {
isMultipleTouchEnabled = false
}

override func draw(_ rect: CGRect) {
strokePaths()
}
}

// private utility methods

private extension Canvas {
func strokePaths() {
if image == nil {
cacheImage()
}

image?.draw(in: bounds)

if let currentScribble = currentScribble {
strokeScribble(currentScribble + (predictivePoints ?? []), isSelected: true)
}
}

func strokeScribble(_ points: [CGPoint], isSelected: Bool = false) {
let path = UIBezierPath(simpleSmooth: points)
let color = isSelected ? selectedStrokeColor : strokeColor
path?.lineCapStyle = .round
path?.lineJoinStyle = .round
path?.lineWidth = lineWidth
color.setStroke()
path?.stroke()
}

func invalidateCachedImage() {
image = nil
setNeedsDisplay()
}

/// caches just the damages, but not the current scribble
func cacheImage() {
guard damages.count > 0 else { return }

image = UIGraphicsImageRenderer(bounds: bounds).image { _ in
for (index, damage) in damages.enumerated() {
strokeScribble(damage.scribble, isSelected: selectedDamageIndex == index)
}
}
}

func append(_ touches: Set<UITouch>, with event: UIEvent?, includePredictive: Bool = false) {
guard let touch = touches.first else { return }

// probably should capture coalesced touches, too
if let touches = event?.coalescedTouches(for: touch) {
currentScribble?.append(contentsOf: touches.map { $0.location(in: self) })
}

currentScribble?.append(touch.location(in: self))

if includePredictive {
predictivePoints = event?
.predictedTouches(for: touch)?
.map { $0.location(in: self) }
} else {
predictivePoints = nil
}

setNeedsDisplay()
}
}

// UIResponder methods
extension Canvas {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let point = touch.location(in: self)
currentScribble = [point]
selectedDamageIndex = nil

isDrawingHandler?(true)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
append(touches, with: event)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
append(touches, with: event, includePredictive: false)

isDrawingHandler?(false)
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
touchesEnded(touches, with: event)
}
}

https://github.com/tygruletz/AddScribblesOnImage/pull/1有关此实现的示例。见 https://github.com/tygruletz/AddScribblesOnImage/pull/2例如,您可能有多个路径作为与特定 DamageItem 关联的一组“涂鸦”。 .

请注意,我个人会在 UIBezierPath 中对描边路径进行“平滑”处理。生成过程但保留用户实际 CGPoint模型对象中的数组。我还建议结合合并触摸(以准确捕捉高帧率设备中的手势)和预测触摸(以避免在 UI 中感知滞后)。所有这些都包含在上述拉取请求中。

不相关,但我可能会提出更多建议:
  • 我会重命名 Canvas成为 CanvasView , 作为 UIView 的子类总是带有后缀View作为惯例;
  • 我可能会建议考虑退出自己绘制路径的过程。我通常会在 CAShapeLayer 中渲染路径子层。这样,您就可以享受 Apple 的优化。
  • 关于ios - 使 UIBezierPath 可选择并更改其颜色,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58435138/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com