SF Symbol 不适用于 scaleAspectFill
SF Symbol does not work well with scaleAspectFill
我注意到 SF 符号不能很好地与 scaleAspectFill
一起使用。
例如,给定一个圆形外观填充带有细边框的 UIImageView,将其图像设置为 UIImage(systemName: "person.crop.circle.fill")
将如下所示。请注意图像并没有一直延伸到边缘。
将图像设置为 UIImage(systemName: "person.fill")
将如下所示。它以某种方式扰乱了自动布局高度约束(即使将 contentMode 设置为 center
也是如此)。
我的解决方法是将 SF 符号导出为 png 并将它们加载到 Xcode,但这当然不理想。我是否错误地使用了 SF 符号?
当我们创建一个带有 SF 符号的 UIImage
时,它会得到一个矢量背衬,并且图像的行为更像是字体字符而不是图像。
如果我们暂时跳过cornerRadius
,会更容易理解。
例如,如果我将标签的文本设置为 O
并给它一个黄色背景,它将看起来像这样:
字符没有到达边界框的边缘。
所以,当我们使用:let thisImage = UIImage(systemName: "person.crop.circle.fill")
并设置图像视图的图像时,我们得到:
正如我们所见,所有 4 个边都有“填充”。
要“删除”填充,我们可以将 CGImage
支持转换为 UIImage
... 但我们需要记住一些事情。
因此,6 个示例 - 每个示例都是此“基本”控制器的子类:
class MyBaseVC: UIViewController {
let imgViewA = UIImageView()
let imgViewB = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
[imgViewA, imgViewB].forEach { v in
// use AspectFill
v.contentMode = .scaleAspectFill
// background color so we can see the framing
v.backgroundColor = .systemYellow
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
imgViewA.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
imgViewA.centerXAnchor.constraint(equalTo: g.centerXAnchor),
imgViewA.widthAnchor.constraint(equalToConstant: 160.0),
imgViewA.heightAnchor.constraint(equalTo: imgViewA.widthAnchor),
imgViewB.topAnchor.constraint(equalTo: imgViewA.bottomAnchor, constant: 20.0),
imgViewB.centerXAnchor.constraint(equalTo: g.centerXAnchor),
imgViewB.widthAnchor.constraint(equalToConstant: 160.0),
imgViewB.heightAnchor.constraint(equalTo: imgViewB.widthAnchor),
])
}
}
第一个示例仅显示了空 yellow-background 图片视图的布局:
class Example1VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
}
}
看起来像这样:
第二个示例从“person.crop.circle.fill”创建图像并设置第一个图像视图:
class Example2VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "default" size
guard let imgA = UIImage(systemName: nm)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
print("imgA size:", imgA.size)
imgViewA.image = imgA
}
}
输出:
到目前为止,没有什么是你没见过的。
代码还将生成的图像大小输出到调试控制台...在这种情况下,imgA size: (20.0, 19.0)
因此,我们将该图像保存为 20x19 png 并将其加载到第二个图像视图中:
class Example3VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "default" size
guard let imgA = UIImage(systemName: nm)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
guard let imgB = UIImage(named: "imgA20x19") else {
fatalError("Could not load imgA20x19")
}
print("imgA:", imgA.size, "imgB:", imgB.size)
imgViewA.image = imgA
imgViewB.image = imgB
}
}
不出所料,因为它现在是位图而不是矢量数据,所以它变得模糊得令人无法接受……而且,我们还没有到边缘:
因此,对于第 4 个示例,我们将使用来自生成的 SF 符号图像的 .cgImage
支持数据来有效地创建位图版本“on-the-fly”:
class Example4VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "default" size
guard let imgA = UIImage(systemName: nm)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
// get a cgRef from imgA
guard let cgRef = imgA.cgImage else {
fatalError("Could not get cgImage!")
}
// create imgB from the cgRef
let imgB = UIImage(cgImage: cgRef, scale: imgA.scale, orientation: imgA.imageOrientation)
.withTintColor(.lightGray, renderingMode: .alwaysOriginal)
print("imgA:", imgA.size, "imgB:", imgB.size)
imgViewA.image = imgA
imgViewB.image = imgB
}
}
越来越近...图像现在到达边缘,但仍然模糊。
为了解决这个问题,我们将在生成初始 SF 符号图像时使用“点”配置:
class Example5VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "160-pts" size
let cfg = UIImage.SymbolConfiguration(pointSize: 160.0)
guard let imgA = UIImage(systemName: nm, withConfiguration: cfg)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
// get a cgRef from imgA
guard let cgRef = imgA.cgImage else {
fatalError("Could not get cgImage!")
}
// create imgB from the cgRef
let imgB = UIImage(cgImage: cgRef, scale: imgA.scale, orientation: imgA.imageOrientation)
.withTintColor(.lightGray, renderingMode: .alwaysOriginal)
print("imgA:", imgA.size, "imgB:", imgB.size)
imgViewA.image = imgA
imgViewB.image = imgB
}
}
调试大小输出显示 imgB: (159.5, 159.5)
(因此,非常接近图像视图的 160x160 大小),它看起来像这样:
我们现在有一个清晰的渲染,没有“填充”......我们可以添加圆角半径和边框:
class Example6VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "160-pts" size
let cfg = UIImage.SymbolConfiguration(pointSize: 160.0)
guard let imgA = UIImage(systemName: nm, withConfiguration: cfg)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
// get a cgRef from imgA
guard let cgRef = imgA.cgImage else {
fatalError("Could not get cgImage!")
}
// create imgB from the cgRef
let imgB = UIImage(cgImage: cgRef, scale: imgA.scale, orientation: imgA.imageOrientation)
.withTintColor(.lightGray, renderingMode: .alwaysOriginal)
print("imgA:", imgA.size, "imgB:", imgB.size)
imgViewA.image = imgA
imgViewB.image = imgB
[imgViewA, imgViewB].forEach { v in
v.layer.cornerRadius = 80
v.layer.borderColor = UIColor.red.cgColor
v.layer.borderWidth = 1
}
}
}
示例 6 结果:
正如您在 post 中提到的那样,您会注意到顶部图像视图并不完全是圆形——也就是说,它以某种方式失去了 1:1 比例。
这是一个古怪的 side-effect 使用 SF Symbols 作为我早已放弃尝试理解的图像。无论约束如何,不同的符号都会导致图像视图改变大小。
你可以在这里看到一些讨论:
我注意到 SF 符号不能很好地与 scaleAspectFill
一起使用。
例如,给定一个圆形外观填充带有细边框的 UIImageView,将其图像设置为 UIImage(systemName: "person.crop.circle.fill")
将如下所示。请注意图像并没有一直延伸到边缘。
将图像设置为 UIImage(systemName: "person.fill")
将如下所示。它以某种方式扰乱了自动布局高度约束(即使将 contentMode 设置为 center
也是如此)。
我的解决方法是将 SF 符号导出为 png 并将它们加载到 Xcode,但这当然不理想。我是否错误地使用了 SF 符号?
当我们创建一个带有 SF 符号的 UIImage
时,它会得到一个矢量背衬,并且图像的行为更像是字体字符而不是图像。
如果我们暂时跳过cornerRadius
,会更容易理解。
例如,如果我将标签的文本设置为 O
并给它一个黄色背景,它将看起来像这样:
字符没有到达边界框的边缘。
所以,当我们使用:let thisImage = UIImage(systemName: "person.crop.circle.fill")
并设置图像视图的图像时,我们得到:
正如我们所见,所有 4 个边都有“填充”。
要“删除”填充,我们可以将 CGImage
支持转换为 UIImage
... 但我们需要记住一些事情。
因此,6 个示例 - 每个示例都是此“基本”控制器的子类:
class MyBaseVC: UIViewController {
let imgViewA = UIImageView()
let imgViewB = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
[imgViewA, imgViewB].forEach { v in
// use AspectFill
v.contentMode = .scaleAspectFill
// background color so we can see the framing
v.backgroundColor = .systemYellow
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
imgViewA.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
imgViewA.centerXAnchor.constraint(equalTo: g.centerXAnchor),
imgViewA.widthAnchor.constraint(equalToConstant: 160.0),
imgViewA.heightAnchor.constraint(equalTo: imgViewA.widthAnchor),
imgViewB.topAnchor.constraint(equalTo: imgViewA.bottomAnchor, constant: 20.0),
imgViewB.centerXAnchor.constraint(equalTo: g.centerXAnchor),
imgViewB.widthAnchor.constraint(equalToConstant: 160.0),
imgViewB.heightAnchor.constraint(equalTo: imgViewB.widthAnchor),
])
}
}
第一个示例仅显示了空 yellow-background 图片视图的布局:
class Example1VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
}
}
看起来像这样:
第二个示例从“person.crop.circle.fill”创建图像并设置第一个图像视图:
class Example2VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "default" size
guard let imgA = UIImage(systemName: nm)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
print("imgA size:", imgA.size)
imgViewA.image = imgA
}
}
输出:
到目前为止,没有什么是你没见过的。
代码还将生成的图像大小输出到调试控制台...在这种情况下,imgA size: (20.0, 19.0)
因此,我们将该图像保存为 20x19 png 并将其加载到第二个图像视图中:
class Example3VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "default" size
guard let imgA = UIImage(systemName: nm)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
guard let imgB = UIImage(named: "imgA20x19") else {
fatalError("Could not load imgA20x19")
}
print("imgA:", imgA.size, "imgB:", imgB.size)
imgViewA.image = imgA
imgViewB.image = imgB
}
}
不出所料,因为它现在是位图而不是矢量数据,所以它变得模糊得令人无法接受……而且,我们还没有到边缘:
因此,对于第 4 个示例,我们将使用来自生成的 SF 符号图像的 .cgImage
支持数据来有效地创建位图版本“on-the-fly”:
class Example4VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "default" size
guard let imgA = UIImage(systemName: nm)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
// get a cgRef from imgA
guard let cgRef = imgA.cgImage else {
fatalError("Could not get cgImage!")
}
// create imgB from the cgRef
let imgB = UIImage(cgImage: cgRef, scale: imgA.scale, orientation: imgA.imageOrientation)
.withTintColor(.lightGray, renderingMode: .alwaysOriginal)
print("imgA:", imgA.size, "imgB:", imgB.size)
imgViewA.image = imgA
imgViewB.image = imgB
}
}
越来越近...图像现在到达边缘,但仍然模糊。
为了解决这个问题,我们将在生成初始 SF 符号图像时使用“点”配置:
class Example5VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "160-pts" size
let cfg = UIImage.SymbolConfiguration(pointSize: 160.0)
guard let imgA = UIImage(systemName: nm, withConfiguration: cfg)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
// get a cgRef from imgA
guard let cgRef = imgA.cgImage else {
fatalError("Could not get cgImage!")
}
// create imgB from the cgRef
let imgB = UIImage(cgImage: cgRef, scale: imgA.scale, orientation: imgA.imageOrientation)
.withTintColor(.lightGray, renderingMode: .alwaysOriginal)
print("imgA:", imgA.size, "imgB:", imgB.size)
imgViewA.image = imgA
imgViewB.image = imgB
}
}
调试大小输出显示 imgB: (159.5, 159.5)
(因此,非常接近图像视图的 160x160 大小),它看起来像这样:
我们现在有一个清晰的渲染,没有“填充”......我们可以添加圆角半径和边框:
class Example6VC: MyBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
let nm = "person.crop.circle.fill"
// create UIImage from SF Symbol at "160-pts" size
let cfg = UIImage.SymbolConfiguration(pointSize: 160.0)
guard let imgA = UIImage(systemName: nm, withConfiguration: cfg)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal) else {
fatalError("Could not load SF Symbol: \(nm)!")
}
// get a cgRef from imgA
guard let cgRef = imgA.cgImage else {
fatalError("Could not get cgImage!")
}
// create imgB from the cgRef
let imgB = UIImage(cgImage: cgRef, scale: imgA.scale, orientation: imgA.imageOrientation)
.withTintColor(.lightGray, renderingMode: .alwaysOriginal)
print("imgA:", imgA.size, "imgB:", imgB.size)
imgViewA.image = imgA
imgViewB.image = imgB
[imgViewA, imgViewB].forEach { v in
v.layer.cornerRadius = 80
v.layer.borderColor = UIColor.red.cgColor
v.layer.borderWidth = 1
}
}
}
示例 6 结果:
正如您在 post 中提到的那样,您会注意到顶部图像视图并不完全是圆形——也就是说,它以某种方式失去了 1:1 比例。
这是一个古怪的 side-effect 使用 SF Symbols 作为我早已放弃尝试理解的图像。无论约束如何,不同的符号都会导致图像视图改变大小。
你可以在这里看到一些讨论: