У меня есть UIView
, которые соответствуют пользовательскому классу Canvas. Это означает, что пользователь может рисовать в этом UIView
.
Каждый раз, когда пользователь заканчивает рисовать, нужно будет нажать кнопку Добавить UIButton
, и строка будет добавлена к UITableView
ниже.
Каждая строка содержит 2 свойства name: String
и scribble: [UInt8]
. Свойство scribble будет содержать позиции X и Y для рисунков, связанных с этой строкой.
Когда пользователь выбирает любую строку из этого UITableView
, тогда цвет пикселей будет изменен на холсте для этого связанного каракуля. .
Здесь у меня есть демоверсия из версии для Android, и мне нужно сделать что-то похожее: http://g.recordit.co/ZY21ufz5kW.gif
А вот мой прогресс в проекте, но я застрял с логикой добавленияКоординаты X и Y, а также я не знаю, как сделать выбор каракули, чтобы иметь возможность изменить цвет на холсте:
https://github.com/tygruletz/AddScribblesOnImage
Вот мой Canvas
класс:
/// 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 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])
}
}
Любая помощь будет высоко ценится! Спасибо за прочтение!