如何使 CAShapeLayer 适合 UIImage 视图?
How to fit CAShape Layer to UIImage View?
我正在解析 SVG 图像文件并将其路径转换为 CAShapeLayer,之后我将此 CAShapeLayer 添加到 UIImage 视图的层。
它工作正常,但问题是它不适合 UIImage 视图。
那是输出。
Image 1。
这是向 UIImageView
添加图层的代码
if let svgURL = Bundle.main.url(forResource: "image", withExtension: "svg") {
let paths = SVGBezierPath.pathsFromSVG(at: svgURL)
for path in paths {
items.append(path)
let layer = createLayer(path: path)
layer.frame = self.backgroundIV.bounds
self.backgroundIV.layer.addSublayer(layer)
}
} // Create Layer from Paths Method
fileprivate func createLayer(path: SVGBezierPath) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
if let any = path.svgAttributes["stroke"] {
shapeLayer.strokeColor = (any as! CGColor)
}
if let any = path.svgAttributes["fill"] {
shapeLayer.fillColor = (any as! CGColor)
}
return shapeLayer
}
我搜索并添加了这两行代码,我得到了这个结果
let scale = CGFloat(0.5)
for path in paths {
path.apply(CGAffineTransform(scaleX: scale, y: scale))
items.append(path)
let layer = createLayer(path: path)
layer.frame = self.backgroundIV.bounds
self.backgroundIV.layer.addSublayer(layer)
}
这是输出。
Image 2
我不知道如何根据 uiImage View bounds
计算 scaleX
或 scaleY
(在 CGAffineTransform
方法中使用)值
编辑
注意:我正在为 CAShapeLayer 应用触摸支持,
那是代码。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
guard let point = touches.first?.location(in: self.backgroundIV),
let layers = self.backgroundIV.layer.sublayers
else { return }
var hitLayers: [CAShapeLayer] = []
selectedLayer?.lineWidth = CGFloat(0)
for subLayer in layers {
if let thisLayer = subLayer as? CAShapeLayer,
let pth = thisLayer.path {
let layerPoint: CGPoint = thisLayer.convert(point, from: self.backgroundIV.layer)
if pth.contains(layerPoint) {
// undoModel.append()
hitLayers.append(thisLayer)
}
}
}
selectedLayer = hitLayers.last
selectedLayer?.strokeColor = UIColor.red.cgColor
selectedLayer?.lineWidth = CGFloat(3)
undo.append(.init(stokeColor: nil, selectedLayer: selectedLayer, points: []))
if ((selectedLayer?.frame.contains(point)) != nil) {
isDragged = true
}
}
根据您更新的问题,使用视图边界计算比例
var paths = [SVGBezierPath]()
override func viewDidLoad() {
super.viewDidLoad()
if let svgURL = Bundle.main.url(forResource: "Freesample", withExtension: "svg") {
paths = SVGBezierPath.pathsFromSVG(at: svgURL)
backgroundIV.layer.borderColor = UIColor.black.withAlphaComponent(0.3).cgColor
backgroundIV.layer.borderWidth = 1
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let svgBounds = SVGBoundingRectForPaths(paths)
var scale = CGFloat.zero
if backgroundIV.bounds.width < backgroundIV.bounds.height {
scale = backgroundIV.bounds.width / svgBounds.width
} else {
scale = backgroundIV.bounds.height / svgBounds.height
}
self.backgroundIV.layer.sublayers?.forEach { [=10=].removeFromSuperlayer() }
for path in paths {
path.apply(.init(scaleX: scale, y: scale))
let layer = createLayer(path: path)
layer.frame = self.backgroundIV.bounds
self.backgroundIV.layer.addSublayer(layer)
}
}
这与我对您之前关于检测多层触摸的问题的回答基本相同。
不同之处在于,此示例使用 SVGImageView
,而不是将您的代码用于 create/add 层,它是 PocketSVG
:
的一部分
class BoxesViewController: UIViewController {
var backgroundIV: SVGImageView!
var detectMode: DetectMode = .All
var detectType: DetectType = .ShapePath
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
guard let svgURL = Bundle.main.url(forResource: "roundedboxes", withExtension: "svg") else {
fatalError("SVG file not found!!!")
}
backgroundIV = SVGImageView.init(contentsOf: svgURL)
backgroundIV.frame = view.bounds
backgroundIV.contentMode = .scaleAspectFit
backgroundIV.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let modeControl = UISegmentedControl(items: ["All", "Top Most", "Bottom Most"])
let typeControl = UISegmentedControl(items: ["Shape Path", "Shape Bounding Box"])
modeControl.translatesAutoresizingMaskIntoConstraints = false
typeControl.translatesAutoresizingMaskIntoConstraints = false
backgroundIV.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(modeControl)
view.addSubview(typeControl)
view.addSubview(backgroundIV)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
modeControl.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
modeControl.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
modeControl.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
typeControl.topAnchor.constraint(equalTo: modeControl.bottomAnchor, constant: 40.0),
typeControl.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
typeControl.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
backgroundIV.topAnchor.constraint(equalTo: typeControl.bottomAnchor, constant: 40.0),
backgroundIV.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
backgroundIV.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
backgroundIV.heightAnchor.constraint(equalTo: backgroundIV.widthAnchor),
])
modeControl.addTarget(self, action: #selector(modeChanged(_:)), for: .valueChanged)
typeControl.addTarget(self, action: #selector(typeChanged(_:)), for: .valueChanged)
modeControl.selectedSegmentIndex = 0
typeControl.selectedSegmentIndex = 0
// so we can see the frame of the image view
backgroundIV.backgroundColor = .white
}
@objc func modeChanged(_ sender: UISegmentedControl) -> Void {
switch sender.selectedSegmentIndex {
case 0:
detectMode = .All
case 1:
detectMode = .TopMost
case 2:
detectMode = .BottomMost
default:
()
}
}
@objc func typeChanged(_ sender: UISegmentedControl) -> Void {
switch sender.selectedSegmentIndex {
case 0:
detectType = .ShapePath
case 1:
detectType = .ShapeBounds
default:
()
}
}
fileprivate func createLayer(path: SVGBezierPath) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
if let any = path.svgAttributes["stroke"] {
shapeLayer.strokeColor = (any as! CGColor)
}
if let any = path.svgAttributes["fill"] {
shapeLayer.fillColor = (any as! CGColor)
}
return shapeLayer
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: self.backgroundIV),
// make sure backgroundIV has sublayers
let layers = self.backgroundIV.layer.sublayers
else { return }
var hitLayers: [CAShapeLayer] = []
// loop through all sublayers
for subLayer in layers {
// make sure
// it is a CAShapeLayer
// it has a path
if let thisLayer = subLayer as? CAShapeLayer,
let pth = thisLayer.path {
// clear the lineWidth... we'll reset it after getting the hit layers
thisLayer.lineWidth = 0
// convert touch point from backgroundIV.layer to thisLayer
let layerPoint: CGPoint = thisLayer.convert(point, from: self.backgroundIV.layer)
if detectType == .ShapePath {
// does the path contain the point?
if pth.contains(layerPoint) {
hitLayers.append(thisLayer)
}
} else if detectType == .ShapeBounds {
if pth.boundingBox.contains(layerPoint) {
hitLayers.append(thisLayer)
}
}
}
}
if detectMode == .All {
hitLayers.forEach { layer in
layer.strokeColor = UIColor.cyan.cgColor
layer.lineWidth = 3
}
} else if detectMode == .TopMost {
if let layer = hitLayers.last {
layer.strokeColor = UIColor.cyan.cgColor
layer.lineWidth = 3
}
} else if detectMode == .BottomMost {
if let layer = hitLayers.first {
layer.strokeColor = UIColor.cyan.cgColor
layer.lineWidth = 3
}
}
}
}
现在 SVG 文件已缩放以适合视图:
我正在解析 SVG 图像文件并将其路径转换为 CAShapeLayer,之后我将此 CAShapeLayer 添加到 UIImage 视图的层。 它工作正常,但问题是它不适合 UIImage 视图。 那是输出。 Image 1。 这是向 UIImageView
添加图层的代码 if let svgURL = Bundle.main.url(forResource: "image", withExtension: "svg") {
let paths = SVGBezierPath.pathsFromSVG(at: svgURL)
for path in paths {
items.append(path)
let layer = createLayer(path: path)
layer.frame = self.backgroundIV.bounds
self.backgroundIV.layer.addSublayer(layer)
}
} // Create Layer from Paths Method
fileprivate func createLayer(path: SVGBezierPath) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
if let any = path.svgAttributes["stroke"] {
shapeLayer.strokeColor = (any as! CGColor)
}
if let any = path.svgAttributes["fill"] {
shapeLayer.fillColor = (any as! CGColor)
}
return shapeLayer
}
我搜索并添加了这两行代码,我得到了这个结果
let scale = CGFloat(0.5)
for path in paths {
path.apply(CGAffineTransform(scaleX: scale, y: scale))
items.append(path)
let layer = createLayer(path: path)
layer.frame = self.backgroundIV.bounds
self.backgroundIV.layer.addSublayer(layer)
}
这是输出。 Image 2
我不知道如何根据 uiImage View bounds
计算scaleX
或 scaleY
(在 CGAffineTransform
方法中使用)值
编辑 注意:我正在为 CAShapeLayer 应用触摸支持, 那是代码。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
guard let point = touches.first?.location(in: self.backgroundIV),
let layers = self.backgroundIV.layer.sublayers
else { return }
var hitLayers: [CAShapeLayer] = []
selectedLayer?.lineWidth = CGFloat(0)
for subLayer in layers {
if let thisLayer = subLayer as? CAShapeLayer,
let pth = thisLayer.path {
let layerPoint: CGPoint = thisLayer.convert(point, from: self.backgroundIV.layer)
if pth.contains(layerPoint) {
// undoModel.append()
hitLayers.append(thisLayer)
}
}
}
selectedLayer = hitLayers.last
selectedLayer?.strokeColor = UIColor.red.cgColor
selectedLayer?.lineWidth = CGFloat(3)
undo.append(.init(stokeColor: nil, selectedLayer: selectedLayer, points: []))
if ((selectedLayer?.frame.contains(point)) != nil) {
isDragged = true
}
}
根据您更新的问题,使用视图边界计算比例
var paths = [SVGBezierPath]()
override func viewDidLoad() {
super.viewDidLoad()
if let svgURL = Bundle.main.url(forResource: "Freesample", withExtension: "svg") {
paths = SVGBezierPath.pathsFromSVG(at: svgURL)
backgroundIV.layer.borderColor = UIColor.black.withAlphaComponent(0.3).cgColor
backgroundIV.layer.borderWidth = 1
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let svgBounds = SVGBoundingRectForPaths(paths)
var scale = CGFloat.zero
if backgroundIV.bounds.width < backgroundIV.bounds.height {
scale = backgroundIV.bounds.width / svgBounds.width
} else {
scale = backgroundIV.bounds.height / svgBounds.height
}
self.backgroundIV.layer.sublayers?.forEach { [=10=].removeFromSuperlayer() }
for path in paths {
path.apply(.init(scaleX: scale, y: scale))
let layer = createLayer(path: path)
layer.frame = self.backgroundIV.bounds
self.backgroundIV.layer.addSublayer(layer)
}
}
这与我对您之前关于检测多层触摸的问题的回答基本相同。
不同之处在于,此示例使用 SVGImageView
,而不是将您的代码用于 create/add 层,它是 PocketSVG
:
class BoxesViewController: UIViewController {
var backgroundIV: SVGImageView!
var detectMode: DetectMode = .All
var detectType: DetectType = .ShapePath
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
guard let svgURL = Bundle.main.url(forResource: "roundedboxes", withExtension: "svg") else {
fatalError("SVG file not found!!!")
}
backgroundIV = SVGImageView.init(contentsOf: svgURL)
backgroundIV.frame = view.bounds
backgroundIV.contentMode = .scaleAspectFit
backgroundIV.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let modeControl = UISegmentedControl(items: ["All", "Top Most", "Bottom Most"])
let typeControl = UISegmentedControl(items: ["Shape Path", "Shape Bounding Box"])
modeControl.translatesAutoresizingMaskIntoConstraints = false
typeControl.translatesAutoresizingMaskIntoConstraints = false
backgroundIV.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(modeControl)
view.addSubview(typeControl)
view.addSubview(backgroundIV)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
modeControl.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
modeControl.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
modeControl.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
typeControl.topAnchor.constraint(equalTo: modeControl.bottomAnchor, constant: 40.0),
typeControl.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
typeControl.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
backgroundIV.topAnchor.constraint(equalTo: typeControl.bottomAnchor, constant: 40.0),
backgroundIV.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
backgroundIV.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
backgroundIV.heightAnchor.constraint(equalTo: backgroundIV.widthAnchor),
])
modeControl.addTarget(self, action: #selector(modeChanged(_:)), for: .valueChanged)
typeControl.addTarget(self, action: #selector(typeChanged(_:)), for: .valueChanged)
modeControl.selectedSegmentIndex = 0
typeControl.selectedSegmentIndex = 0
// so we can see the frame of the image view
backgroundIV.backgroundColor = .white
}
@objc func modeChanged(_ sender: UISegmentedControl) -> Void {
switch sender.selectedSegmentIndex {
case 0:
detectMode = .All
case 1:
detectMode = .TopMost
case 2:
detectMode = .BottomMost
default:
()
}
}
@objc func typeChanged(_ sender: UISegmentedControl) -> Void {
switch sender.selectedSegmentIndex {
case 0:
detectType = .ShapePath
case 1:
detectType = .ShapeBounds
default:
()
}
}
fileprivate func createLayer(path: SVGBezierPath) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
if let any = path.svgAttributes["stroke"] {
shapeLayer.strokeColor = (any as! CGColor)
}
if let any = path.svgAttributes["fill"] {
shapeLayer.fillColor = (any as! CGColor)
}
return shapeLayer
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: self.backgroundIV),
// make sure backgroundIV has sublayers
let layers = self.backgroundIV.layer.sublayers
else { return }
var hitLayers: [CAShapeLayer] = []
// loop through all sublayers
for subLayer in layers {
// make sure
// it is a CAShapeLayer
// it has a path
if let thisLayer = subLayer as? CAShapeLayer,
let pth = thisLayer.path {
// clear the lineWidth... we'll reset it after getting the hit layers
thisLayer.lineWidth = 0
// convert touch point from backgroundIV.layer to thisLayer
let layerPoint: CGPoint = thisLayer.convert(point, from: self.backgroundIV.layer)
if detectType == .ShapePath {
// does the path contain the point?
if pth.contains(layerPoint) {
hitLayers.append(thisLayer)
}
} else if detectType == .ShapeBounds {
if pth.boundingBox.contains(layerPoint) {
hitLayers.append(thisLayer)
}
}
}
}
if detectMode == .All {
hitLayers.forEach { layer in
layer.strokeColor = UIColor.cyan.cgColor
layer.lineWidth = 3
}
} else if detectMode == .TopMost {
if let layer = hitLayers.last {
layer.strokeColor = UIColor.cyan.cgColor
layer.lineWidth = 3
}
} else if detectMode == .BottomMost {
if let layer = hitLayers.first {
layer.strokeColor = UIColor.cyan.cgColor
layer.lineWidth = 3
}
}
}
}
现在 SVG 文件已缩放以适合视图: