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 作为我早已放弃尝试理解的图像。无论约束如何,不同的符号都会导致图像视图改变大小。

你可以在这里看到一些讨论: