如何在 SwiftUI 中显示整个 CALayer 内容?
How could I display the entire CALayer content in the SwiftUI?
我的代码很简单如下:
import PocketSVG
import SwiftUI
struct SVGImage: UIViewRepresentable {
var SVGName: String?
var tintStrokeColor: CGColor?
var tintFillColor: CGColor?
func makeUIView(context: Context) -> UIView {
return UIView()
}
func updateUIView(_ view: UIView, context: Context) {
let svgURL = Bundle.main.url(forResource: "tiger", withExtension: "svg")!
let paths = SVGBezierPath.pathsFromSVG(at: svgURL)
let layer = CALayer()
for path in paths {
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)
}
layer.addSublayer(shapeLayer)
}
layer.contentsGravity = CALayerContentsGravity.resizeAspectFill
layer.contentsScale = UIScreen.main.scale
view.layer.addSublayer(layer)
}
}
struct SVGImage_Previews: PreviewProvider {
static var previews: some View {
SVGImage()
}
}
但是在View
中并没有显示整个图像,只显示了一部分。
我也设置了CALayerContentsGravity.resizeAspectFill
和UIScreen.main.scale
,但是没有生效
这是一个相对容易的修复:
layer.position = svgView.center
并将图层添加到 UIView
,如下所示:
import PocketSVG
import SwiftUI
struct SVGImage: UIViewRepresentable {
var SVGName: String?
var tintStrokeColor: CGColor?
var tintFillColor: CGColor?
func makeUIView(context: Context) -> UIView {
let svgView = UIView(frame: UIScreen.main.bounds)
svgView.contentMode = .scaleAspectFit
let svgURL = Bundle.main.url(forResource: "tiger", withExtension: "svg")!
let paths = SVGBezierPath.pathsFromSVG(at: svgURL)
let layer = CALayer()
for path in paths {
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)
}
layer.addSublayer(shapeLayer)
}
layer.position = svgView.center
svgView.layer.addSublayer(layer)
return svgView
}
func updateUIView(_ view: UIView, context: Context) {
}
}
struct SVGImage_Previews: PreviewProvider {
static var previews: some View {
SVGImage()
}
}
import SwiftUI
// It works on iPhone, iPad 13+ and macOS 10.15+, yep with UIView.
// Persistent root view we going to use
// Touches can be also intercepted by subclassing it
let viewRoot: UIView = UIView()
// CALayer : This is "The One"
// Can use any CALayer subclass
let layerOne: CALayer = CALayer()
// Binding to change "The One" geometry
let layerOneBinding: Binding<CGRect> = .init(get: { () -> CGRect in
CGRect(origin: layerOne.position, size: layerOne.bounds.size)
}, set: { (newValue: CGRect) in
layerOne.position = newValue.origin
layerOne.bounds.size = newValue.size
})
// This is UIViewRepresentable for "The One"
struct ViewRootRepresenter: UIViewRepresentable {
// Think as it's initializer
func makeUIView(context: Context) -> UIView {
// All objects better be persistently pre-initialized elsewhere, use only references.
// Use this method only to pass references, extra adjustments to UIView or CALayer apply outside SwiftUI.
// ...
// ...but for testing only:
// Make UIView red to see it
viewRoot.backgroundColor = UIColor.red
// Make "The One" green to see it
layerOne.backgroundColor = UIColor.green.cgColor
// ...
// add LayerOne to view hierarchy so it draws, but better to have it done elsewhere
viewRoot.layer.addSublayer(layerOne)
// And just all together as UIView
return viewRoot
}
//
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<ViewRootRepresenter>) {
print("updateUIView")
// Protocol required
// Do whatever you think you can do here
}
}
struct GeometryGetter: View {
// "Binding" only to pass geometry changes towards "The One"
@Binding var rectBinded: CGRect
// Look carefully what is actually returned...
var body: some View {
return GeometryReader { geometry in
self.makeView(geometry: geometry)
}
}
func makeView(geometry: GeometryProxy) -> some View {
// Update "Bounding" to resize "The One"
DispatchQueue.main.async {
self.rectBinded = geometry.frame(in: .global)
}
// Return ViewRootRepresenter here :)
return ViewRootRepresenter()
// ...due to use of persistent pre-initialized UIView and CALayer no objects initialize whithin SwiftUI pipeline
}
}
// ContentView first on pipeline
// ...think as SwiftUI's RootView
struct ContentView: View {
var body: some View {
// Use GeometryGetter here, through layerRootBinding it applies geometry to "The One"
GeometryGetter(rectBinded: layerOneBinding)
}
}
我的代码很简单如下:
import PocketSVG
import SwiftUI
struct SVGImage: UIViewRepresentable {
var SVGName: String?
var tintStrokeColor: CGColor?
var tintFillColor: CGColor?
func makeUIView(context: Context) -> UIView {
return UIView()
}
func updateUIView(_ view: UIView, context: Context) {
let svgURL = Bundle.main.url(forResource: "tiger", withExtension: "svg")!
let paths = SVGBezierPath.pathsFromSVG(at: svgURL)
let layer = CALayer()
for path in paths {
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)
}
layer.addSublayer(shapeLayer)
}
layer.contentsGravity = CALayerContentsGravity.resizeAspectFill
layer.contentsScale = UIScreen.main.scale
view.layer.addSublayer(layer)
}
}
struct SVGImage_Previews: PreviewProvider {
static var previews: some View {
SVGImage()
}
}
但是在View
中并没有显示整个图像,只显示了一部分。
我也设置了CALayerContentsGravity.resizeAspectFill
和UIScreen.main.scale
,但是没有生效
这是一个相对容易的修复:
layer.position = svgView.center
并将图层添加到 UIView
,如下所示:
import PocketSVG
import SwiftUI
struct SVGImage: UIViewRepresentable {
var SVGName: String?
var tintStrokeColor: CGColor?
var tintFillColor: CGColor?
func makeUIView(context: Context) -> UIView {
let svgView = UIView(frame: UIScreen.main.bounds)
svgView.contentMode = .scaleAspectFit
let svgURL = Bundle.main.url(forResource: "tiger", withExtension: "svg")!
let paths = SVGBezierPath.pathsFromSVG(at: svgURL)
let layer = CALayer()
for path in paths {
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)
}
layer.addSublayer(shapeLayer)
}
layer.position = svgView.center
svgView.layer.addSublayer(layer)
return svgView
}
func updateUIView(_ view: UIView, context: Context) {
}
}
struct SVGImage_Previews: PreviewProvider {
static var previews: some View {
SVGImage()
}
}
import SwiftUI
// It works on iPhone, iPad 13+ and macOS 10.15+, yep with UIView.
// Persistent root view we going to use
// Touches can be also intercepted by subclassing it
let viewRoot: UIView = UIView()
// CALayer : This is "The One"
// Can use any CALayer subclass
let layerOne: CALayer = CALayer()
// Binding to change "The One" geometry
let layerOneBinding: Binding<CGRect> = .init(get: { () -> CGRect in
CGRect(origin: layerOne.position, size: layerOne.bounds.size)
}, set: { (newValue: CGRect) in
layerOne.position = newValue.origin
layerOne.bounds.size = newValue.size
})
// This is UIViewRepresentable for "The One"
struct ViewRootRepresenter: UIViewRepresentable {
// Think as it's initializer
func makeUIView(context: Context) -> UIView {
// All objects better be persistently pre-initialized elsewhere, use only references.
// Use this method only to pass references, extra adjustments to UIView or CALayer apply outside SwiftUI.
// ...
// ...but for testing only:
// Make UIView red to see it
viewRoot.backgroundColor = UIColor.red
// Make "The One" green to see it
layerOne.backgroundColor = UIColor.green.cgColor
// ...
// add LayerOne to view hierarchy so it draws, but better to have it done elsewhere
viewRoot.layer.addSublayer(layerOne)
// And just all together as UIView
return viewRoot
}
//
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<ViewRootRepresenter>) {
print("updateUIView")
// Protocol required
// Do whatever you think you can do here
}
}
struct GeometryGetter: View {
// "Binding" only to pass geometry changes towards "The One"
@Binding var rectBinded: CGRect
// Look carefully what is actually returned...
var body: some View {
return GeometryReader { geometry in
self.makeView(geometry: geometry)
}
}
func makeView(geometry: GeometryProxy) -> some View {
// Update "Bounding" to resize "The One"
DispatchQueue.main.async {
self.rectBinded = geometry.frame(in: .global)
}
// Return ViewRootRepresenter here :)
return ViewRootRepresenter()
// ...due to use of persistent pre-initialized UIView and CALayer no objects initialize whithin SwiftUI pipeline
}
}
// ContentView first on pipeline
// ...think as SwiftUI's RootView
struct ContentView: View {
var body: some View {
// Use GeometryGetter here, through layerRootBinding it applies geometry to "The One"
GeometryGetter(rectBinded: layerOneBinding)
}
}