gpt4 book ai didi

ios - 如何在 Swift 中使用 CAShapeLayer 为 CALayer.SubLayer 创建橡皮擦

转载 作者:行者123 更新时间:2023-11-30 10:33:47 29 4
gpt4 key购买 nike

我一直在到处寻找一个已经被问过很多次的问题的答案。我花了几个小时浏览 SO 和 Google。必须有一个不需要费力搬山的答案。

我正在开发一个矢量绘图应用程序,并终于使绘图和撤消功能正常工作。现在我需要一个橡皮擦:-o

编辑:根据@DonMag 的精彩文章,我能够非常接近橡皮擦,但有些东西仍然不太正确。因此,我将尝试解释我的 View 和图层在应用程序中的情况以及为什么我这样做:

从底部 View /层开始到顶部...

  1. BackgroundImageView - 我使用此 ImageView 来保存绘图表面的“背景”。它是一个可以更改的层,可以保存和调用新的"template"。我将其分开,以便用户无法删除绘图表面。背景由 CAShapeLayers 组成,绘制代表不同的纸张类型。

  2. MainImageView - 我使用此 ImageView 来完成用户启动的所有绘图。因此,我触摸并拖动手指,新的 CAShapeLayer 就会添加到 ImageView 中。这使得用户的绘图与“绘图表面”分开。这也是我想要删除的地方

  3. PageImagesView - 我使用此 View 来保存用户可以添加到页面的图像,并移动/调整它们的大小。我不希望橡皮擦影响图像,但如果在 MainImageView 中绘制的一条线穿过图像并且需要删除,它应该让图像显示出来,而不是删除图像的部分内容。

    <

我还添加了另一个层,试图让橡皮擦工作,并将其命名为“EraserImageView”,并将“蒙版”绘制到其中,然后尝试将该蒙版应用到 MainImageView。

这是我的绘图代码,每次调用 TouchMoved 时都会调用:

编辑:将橡皮擦代码添加到我的绘图代码中。

 if eraser {
let linePath = UIBezierPath()

for (index, point) in line.enumerated() {
if index == 0 {
midPoint = CGPoint(
x: (point.x + point.x) / 2,
y: (point.y + point.y) / 2
)
linePath.move(to: midPoint!)
} else {
midPoint = CGPoint(
x: (point.x + line[index - 1].x) / 2,
y: (point.y + line[index - 1].y) / 2
)
linePath.addQuadCurve(to: midPoint!, controlPoint: line[index - 1])
}
}

let maskLayer = CAShapeLayer()
maskLayer.lineWidth = brush
maskLayer.lineCap = .round
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.fillColor = nil
maskLayer.frame = backgroundImageView.bounds
maskLayer.path = linePath.cgPath
//eraserImageView.layer.addSublayer(backgroundImageView.layer)
eraserImageView.layer.addSublayer(maskLayer)
eraserImageView.layer.mask = mainImageView.layer
}

上面的代码会导致除“橡皮擦”触摸的部分之外的所有用户绘图消失。我知道我的情况有些不对劲,或者我敷面膜的方式不正确。有没有人有解决办法?

画一些线条,看起来很棒......

[I draw some lines, and looks great!

当我尝试使用橡皮擦时,就会发生这种情况......

When I start to erase everything disappears except for the spot I touched with the eraser.

正如您在上面看到的,我可以画线,但是一旦我将橡皮擦触摸到页面,它就会删除除我用橡皮擦触摸的部分之外的所有内容。

有谁知道我哪里出错了?

编辑:如此接近!当我移动手指时,我能够让橡皮擦删除部分绘制的线条。但它不是使用尺寸进行绘图,而是制作形状。当我使用橡皮擦后触摸绘图表面时,它也会替换所有“删除”的部分。

这是我的新橡皮擦代码:

if eraser {
//var rect: CGRect = CGRect()
let linePath = UIBezierPath(rect: mainImageView.bounds)

for (index, point) in line.enumerated() {
if index == 0 {
midPoint = CGPoint(
x: (point.x + point.x) / 2,
y: (point.y + point.y) / 2
)
//rect = CGRect(x: midPoint!.x, y: midPoint!.y, width: brush, height: brush)
linePath.move(to: midPoint!)
} else {
midPoint = CGPoint(
x: (point.x + line[index - 1].x) / 2,
y: (point.y + line[index - 1].y) / 2
)
//rect = CGRect(x: midPoint!.x, y: midPoint!.y, width: brush, height: brush)
linePath.addQuadCurve(to: midPoint!, controlPoint: line[index - 1])
}
}

let maskLayer = CAShapeLayer()
maskLayer.lineWidth = brush
maskLayer.lineCap = .round
maskLayer.strokeColor = UIColor.clear.cgColor
maskLayer.fillColor = UIColor.black.cgColor
maskLayer.opacity = 1.0
maskLayer.path = linePath.cgPath
maskLayer.fillRule = .evenOdd
mainImageView.layer.addSublayer(maskLayer)
mainImageView.layer.mask = maskLayer

}

结果如下: enter image description here

关于如何让橡皮擦像线条一样绘制有什么想法吗?

编辑:应@DonMag的要求添加背景“绘图”的代码

import Foundation
import UIKit

class DrawBulletLayer : UIView {

private var bullet: CAShapeLayer?

func drawBullets(coordinates: UIImageView, bulletColor: UIColor) -> CALayer {
let bullet = self.bullet ?? CAShapeLayer()
let bulletPath = UIBezierPath()

bullet.contentsScale = UIScreen.main.scale

var bullets: [CGPoint] = []
let width = coordinates.frame.width
let height = coordinates.frame.height

let widthBullets = CGFloat(width / 55)
let heightBullets = CGFloat(height / 39)

var hb: CGFloat?
var wb: CGFloat?

for n in 1...39 {
hb = heightBullets * CGFloat(n)
for o in 1...55 {
wb = widthBullets * CGFloat(o)
bullets.append(CGPoint(x: wb!, y: hb!))
}
}

UIColor.black.setStroke()

bullets.forEach { point in
bulletPath.move(to: point)
bulletPath.addLine(to: point)
}

bullet.path = bulletPath.cgPath
bullet.opacity = 1.0
bullet.lineWidth = 2.0
bullet.lineCap = .round
bullet.fillColor = UIColor.clear.cgColor
bullet.strokeColor = bulletColor.cgColor

if self.bullet == nil {
self.bullet = bullet
layer.addSublayer(bullet)
}

return layer
}
}

以下是将其添加到BackgroundImageView的方法:

func updateTemplate() {
let templates = TemplatePickerData()
var loadLayer = templates.loadTemplateIds()
if loadLayer.count == 0 {
_ = templates.loadTemplates()
loadLayer = templates.loadTemplateIds()
}
print("this is the template ID: \(templateId)")
//let templateId = loadLayer[template].value(forKey: "templateId") as! Int
if template < 0 {
template = 0
}

switch template {
case 0:
//scrollView.image = UIImage(named: "habitTracker0")!
scrollView.backgroundImageView.layer.sublayers?.removeAll()
scrollView.backgroundImageView.layer.addSublayer(drawBullets.drawBullets(coordinates: scrollView.backgroundImageView, bulletColor: UIColor(red: 214.0/255.0, green: 214.0/255.0, blue: 214.0/255.0, alpha: 1.0)))
scrollView.setNeedsLayout()
scrollView.layoutIfNeeded()
scrollView.setNeedsDisplay()
case 1:
//scrollView.image = UIImage(named: "monthTemplate0")!
scrollView.backgroundImageView.layer.sublayers?.removeAll()
scrollView.backgroundImageView.layer.addSublayer(drawNotes.drawLines(coordinates: scrollView.backgroundImageView, lineColor: UIColor(red: 214.0/255.0, green: 214.0/255.0, blue: 214.0/255.0, alpha: 1.0)))
scrollView.setNeedsLayout()
scrollView.layoutIfNeeded()
scrollView.setNeedsDisplay()
case 2:
//scrollView.image = UIImage(named: "habitTracker0")!
scrollView.backgroundImageView.layer.sublayers?.removeAll()
scrollView.backgroundImageView.layer.addSublayer(drawNotes2.drawLines(coordinates: scrollView.backgroundImageView, lineColor: UIColor(red: 214.0/255.0, green: 214.0/255.0, blue: 214.0/255.0, alpha: 1.0)))
scrollView.setNeedsLayout()
scrollView.layoutIfNeeded()
scrollView.setNeedsDisplay()
default:
if loadLayer.count > template {
template = 0
}
print("this layer is named: \(loadLayer[template].value(forKey: "templateName") as! String)")
let layer = loadLayer[template].value(forKey: "templatePath") as! String
templateId = loadLayer[template].value(forKey: "templateId") as! Int
let thisTemplate = templates.loadImage(image: layer)

scrollView.backgroundImageView.layer.sublayers?.removeAll()
scrollView.backgroundImageView.layer.addSublayer(drawBullets.drawBullets(coordinates: scrollView.backgroundImageView, bulletColor: UIColor(red: 214.0/255.0, green: 214.0/255.0, blue: 214.0/255.0, alpha: 1.0)))
scrollView.backgroundImageView.layer.addSublayer(thisTemplate)
scrollView.setNeedsLayout()
scrollView.layoutIfNeeded()
scrollView.setNeedsDisplay()
}
scrollView.setNeedsDisplay()

if optionsMenuView.pageNameTextField.text != "" {
if isYear {
page = optionsMenuView.savePage(journalName: journalName, monthName: nil, weekName: nil, yearName: yearName, yearPosition: yearPosition, pageDrawingPath: pageDrawingPath, originalName: originalYearName, brushColor: 1, brushSize: brushSizeMenuView.brushSlider.value, templateId: templateId, pageDrawing: scrollView.mainImageView.layer)
} else {
page = optionsMenuView.savePage(journalName: journalName, monthName: monthName, weekName: weekName, yearName: nil, yearPosition: nil, pageDrawingPath: pageDrawingPath, originalName: originalWeekName, brushColor: 1, brushSize: brushSizeMenuView.brushSlider.value, templateId: templateId, pageDrawing: scrollView.mainImageView.layer)
}
}
optionsMenuView.templateId = templateId
}

希望对更多人有帮助...

最佳答案

删除贝塞尔曲线路径的一部分会很棘手...您可能需要计算交点(笔画宽度的交点,而不仅仅是路径本身的交点)并将现有线分成多个段.

这是另一种方法 - 不确定它是否适合您,但可能值得考虑:

enter image description here

“绘图”图层可能是您已经拥有的图层。 “橡皮擦”图层将包含背景图像,然后“线条”(贝塞尔路径)将用作 mask ,因此它看起来会删除下面图层的部分。 p>

最后一行为黄色“绘图”图层:

enter image description here

最后一行作为“橡皮擦”层:

enter image description here

这是我为此使用的代码。我认为展示这个想法非常简单。没有实际的“绘图”功能 - 它只是使用一组硬编码的坐标和属性,就好像它们是通过触摸跟踪生成的一样。

当您运行它时,顶部的按钮将添加红色、绿色和蓝色“线”,然后将在“黄线”和“橡皮擦线”之间切换最后一组点。

//
// ViewController.swift
// VectorDrawTest
//
// Created by Don Mag on 8/8/19.
//

import UIKit

enum LineType: Int {
case DRAW
case ERASE
}

class LineDef: NSObject {
var lineType: LineType = .DRAW
var color: UIColor = UIColor.black
var opacity: Float = 1.0
var lineWidth: CGFloat = 8.0
var points: [CGPoint] = [CGPoint]()
}

class DrawingView: UIView {

// the background image
var bkgImage: UIImage = UIImage() {
didSet {
updateBkgImage()
}
}

func updateBkgImage() -> Void {
// if no layers have been added yet, add the background image layer
if layer.sublayers == nil {
let l = CALayer()
layer.addSublayer(l)
}
guard let layers = layer.sublayers else { return }
for l in layers {
if let _ = l as? CAShapeLayer {
// in case we're changing the backgound image after lines have been drawn
// ignore shape layers
} else {
// this layer is NOT a CAShapeLayer, so it's either the first (background image) layer
// or it's an eraser layer, so update the contents
l.contents = bkgImage.cgImage
}
}
setNeedsDisplay()
}

func undo() -> Void {
// only remove a layer if it's not the first (background image) layer
guard let n = layer.sublayers?.count, n > 1 else { return }
_ = layer.sublayers?.popLast()
}

func addLineDef(_ def: LineDef) -> Void {

if def.lineType == LineType.DRAW {

// create new shape layer
let newLayer = CAShapeLayer()

// set "draw" properties
newLayer.lineCap = .round
newLayer.lineWidth = def.lineWidth
newLayer.opacity = def.opacity
newLayer.strokeColor = def.color.cgColor
newLayer.fillColor = UIColor.clear.cgColor

// create bezier path from LineDef points
let drawPts = def.points
let bez = UIBezierPath()
for pt in drawPts {
if pt == drawPts.first {
bez.move(to: pt)
} else {
bez.addLine(to: pt)
}
}
// set path
newLayer.path = bez.cgPath

// add layer
layer.addSublayer(newLayer)

} else {

// create new layer
let newLayer = CALayer()
// set its contents to the background image
newLayer.contents = bkgImage.cgImage
newLayer.opacity = def.opacity

// create a shape layer to use as a mask
let maskLayer = CAShapeLayer()

// set "draw" properties
// strokeColor will always be black, because it just uses alpha for the mask
maskLayer.lineCap = .round
maskLayer.lineWidth = def.lineWidth
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.fillColor = UIColor.clear.cgColor

// add mask
newLayer.mask = maskLayer

// create bezier path from LineDef points
let drawPts = def.points
let bez = UIBezierPath()
for pt in drawPts {
if pt == drawPts.first {
bez.move(to: pt)
} else {
bez.addLine(to: pt)
}
}
// set maskLayer's path
maskLayer.path = bez.cgPath

// add layer
layer.addSublayer(newLayer)

}

setNeedsDisplay()
}

override func layoutSubviews() {
super.layoutSubviews()

// update layer frames
if let layers = layer.sublayers {
for l in layers {
l.frame = bounds
}
}
}

}


class DrawViewController: UIViewController {

let theDrawingView: DrawingView = {
let v = DrawingView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()

let demoButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
v.setTitleColor(.blue, for: .normal)
v.setTitleColor(.lightGray, for: .highlighted)
v.setTitle("Draw Red", for: .normal)
return v
}()

let redLine: LineDef = {
let d = LineDef()
d.lineType = .DRAW
d.color = .red
d.lineWidth = 8.0
d.points = [
CGPoint(x: 20, y: 20),
CGPoint(x: 40, y: 140),
CGPoint(x: 280, y: 200),
]
return d
}()

let greenLine: LineDef = {
let d = LineDef()
d.lineType = .DRAW
d.color = .green
d.lineWidth = 16.0
d.points = [
CGPoint(x: 20, y: 100),
CGPoint(x: 80, y: 80),
CGPoint(x: 240, y: 140),
CGPoint(x: 100, y: 200),
]
return d
}()

let blueLine: LineDef = {
let d = LineDef()
d.lineType = .DRAW
d.color = .blue
d.opacity = 0.5
d.lineWidth = 24.0
d.points = [
CGPoint(x: 250, y: 20),
CGPoint(x: 150, y: 240),
CGPoint(x: 100, y: 60),
]
return d
}()

let yellowLine: LineDef = {
let d = LineDef()
d.lineType = .DRAW
d.color = .yellow
d.lineWidth = 32.0
d.points = [
CGPoint(x: 30, y: 200),
CGPoint(x: 250, y: 80),
CGPoint(x: 250, y: 180),
]
return d
}()

let eraserLine: LineDef = {
let d = LineDef()
d.lineType = .ERASE
d.lineWidth = 32.0
d.points = [
CGPoint(x: 30, y: 200),
CGPoint(x: 250, y: 80),
CGPoint(x: 250, y: 180),
]
return d
}()

var testErase = false

override func viewDidLoad() {
super.viewDidLoad()

// add the drawing view
view.addSubview(theDrawingView)

// constrain it 300 x 300 centered X and Y
NSLayoutConstraint.activate([
theDrawingView.widthAnchor.constraint(equalToConstant: 300),
theDrawingView.heightAnchor.constraint(equalToConstant: 300),
theDrawingView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
theDrawingView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])

let imgName = "TheCat"
if let img = UIImage(named: imgName) {
theDrawingView.bkgImage = img
}

// add a demo button
view.addSubview(demoButton)

// constrain it 20-pts from the top, centered X
NSLayoutConstraint.activate([
demoButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
demoButton.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
demoButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])

// add the touchUpInside target
demoButton.addTarget(self, action: #selector(doTest), for: .touchUpInside)
}

@objc func doTest(_ sender: Any?) -> Void {

if let b = sender as? UIButton {

let t = b.currentTitle

switch t {
case "Draw Red":
theDrawingView.addLineDef(redLine)
b.setTitle("Draw Green", for: .normal)
case "Draw Green":
theDrawingView.addLineDef(greenLine)
b.setTitle("Draw Blue", for: .normal)
case "Draw Blue":
theDrawingView.addLineDef(blueLine)
b.setTitle("Draw Yellow", for: .normal)
case "Draw Yellow":
theDrawingView.addLineDef(yellowLine)
b.setTitle("Toggle Yellow / Erase", for: .normal)
default:
toggle()
}

}
}

func toggle() -> Void {

// undo the last action
theDrawingView.undo()

// toggle bool var
testErase = !testErase

// add either yellowLine or eraserLine
theDrawingView.addLineDef(testErase ? eraserLine : yellowLine)

}

}

一切都是通过代码完成的 - 没有 @IBOutlets@IBActions - 所以只需启动一个新项目并将 ViewController.swift 替换为以上代码。

关于ios - 如何在 Swift 中使用 CAShapeLayer 为 CALayer.SubLayer 创建橡皮擦,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58546823/

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