SF符号与ScaleSpectFill无法正常工作

发布于 2025-01-18 04:05:34 字数 713 浏览 4 评论 0原文

我注意到SF符号与scaleaspectfill的真正效果不佳。

例如,给定一个圆形方面填充uiimageView用薄边框填充,将其图像设置为uiimage(systemName:“ person.crop.circle.fill”)看起来如下。请注意,图像不会一直延伸到边缘。

将图像设置为uiimage(SystemName:“ person.fill”)看起来像这样。不知何故,它会弄乱自动布局高度约束(即使将ContentMode设置为Center)也是如此。

我的解决方法是将SF符号导出为PNG并将其加载到Xcode中,但这当然不是理想的。我是否错误地使用SF符号?

I noticed that SF symbol doesn't really work well with scaleAspectFill.

For instance, given a circular aspect fill UIImageView with a thin border, setting its image to UIImage(systemName: "person.crop.circle.fill") would look like the following. Notice the image doesn't extend all the way to the edge.

enter image description here

Setting the image to UIImage(systemName: "person.fill") would look like this. Somehow it messes up the auto layout height constraint (this is the case even when contentMode is set to center).

enter image description here

My workaround is to export SF symbol as png and load them into Xcode, but this is not ideal of course. Am I using SF symbols incorrectly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

土豪 2025-01-25 04:05:34

当我们使用 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),
            
        ])
        
    }
    
}

第一个示例仅显示带有空黄色背景图像视图的布局:

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
    }
}

正如预期的那样,因为它现在是位图而不是矢量数据,所以它变得难以接受的模糊......而且,我们仍然没有达到边缘:

在此处输入图像描述

因此,对于第四个示例,我们将使用生成的 SF Symbol 图像中的 .cgImage 支持数据来有效地“即时”创建位图版本:

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
    }
}

<一个href="https://i.sstatic.net/sQoZo.png" rel="nofollow noreferrer">在此处输入图像描述

越来越近...图像现在到达边缘,但仍然模糊。

为了解决这个问题,我们在生成初始 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 结果:

在此处输入图像描述

正如您在帖子中提到的,您会注意到顶部图像视图并不完全是圆形的 - 也就是说,它以某种方式失去了 1:1 的比例。

这是使用 SF 符号作为图像的一个奇怪的副作用,我早已放弃尝试理解它。无论约束如何,不同的符号都会导致图像视图改变大小。

您可以在此处查看一些讨论:https://stackoverflow.com/a/66293917/6257435

When we create a UIImage with an SF Symbol, it gets a vector backing and the image behaves much more like a font character than an image.

It will be much easier to understand if we skip the cornerRadius for now.

For example, if I set the text of a label to O and give it a yellow background, it will look like this:

enter image description here

The character does not reach to the edges of the bounding box.

So, when we use: let thisImage = UIImage(systemName: "person.crop.circle.fill") and set an image view's image, we get this:

enter image description here

As we see, there is "padding" on all 4 sides.

To "remove" the padding, we can convert the CGImage backing to a UIImage ... but we need to keep a few things in mind.

So, 6 examples - each example is a subclass of this "base" controller:

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),
            
        ])
        
    }
    
}

The first example just shows the layout with empty yellow-background image views:

class Example1VC: MyBaseVC {
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

and it looks like this:

enter image description here

The 2nd example create an image from "person.crop.circle.fill" and sets the first image view:

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
    }
}

Output:

enter image description here

So far, nothing you haven't seen yet.

The code also output the generated image size to the debug console... in this case, imgA size: (20.0, 19.0)

So, we save out that image as a 20x19 png and load it into the 2nd image view:

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
    }
}

As expected, because its now a bitmap instead of vector data, it gets unacceptably fuzzy... and, we're still not to the edges:

enter image description here

So, for the 4th example, we'll use the .cgImage backing data from the generated SF Symbol image to effectively create the bitmap version "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
    }
}

enter image description here

Getting closer... the image now reaches the edges, but is still blurry.

To fix that, we'll use a "point" configuration when we generate the initial SF Symbol image:

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
    }
}

The debug size output shows imgB: (159.5, 159.5) (so, pretty close to 160x160 size of the image view), and it looks like this:

enter image description here

We now have a crisp rendering, without the "padding" ... and we can add the corner radius and border:

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
        }
    }
    
}

Example 6 result:

enter image description here

As you mentioned in your post, you'll notice the top image view is not exactly round -- that is, it somehow lost its 1:1 ratio.

This is a quirky side-effect of using SF Symbols as images that I have long since given up trying to understand. Different symbols will cause the image view to change size, regardless of constraints.

You can see some discussion here: https://stackoverflow.com/a/66293917/6257435

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文