qt自定义界面生成器_在界面生成器中设计自定义UICollectionViewListCell

qt自定义界面生成器

This article is originally published at https://swiftsenpai.com on August 22, 2020

本文最初于 2020年8月22日 发布在 https://swiftsenpai.com

Since the publishing of my article “UICollectionView List with Custom Cell and Custom Configuration”, a lot of my readers have been asking me whether it is possible to design the custom UICollectionViewListCell in the interface builder.

自从发表文章“ 具有自定义单元格和自定义配置的UICollectionView列表以来 ,许多读者一直在问我是否可以在界面构建器中设计自定义UICollectionViewListCell

Well, the answer is definitely yes!

好吧,答案肯定是肯定的

Since the UICollectionViewListCell's content view is just a UIView subclass that conforms to the UIContentView protocol, theoretically as long as we are able to link up the content view with the interface builder then everything should work accordingly.

由于UICollectionViewListCell的内容视图只是符合UIContentView协议的UIView子类,因此UIContentView理论上讲,只要我们能够将内容视图与接口生成器链接起来,则所有内容都应相应地工作。

After spending some time experimenting in Xcode, I have successfully created a UICollectionViewListCell content view using interface builder. On top of that, I also discovered that you can even manipulate the cell height by adjusting the auto layout constraints within the cell's content view.

花了一些时间在Xcode中进行实验之后,我已经使用界面构建器成功创建了UICollectionViewListCell内容视图。 最重要的是,我还发现您甚至可以通过调整单元格内容视图中的自动布局约束来操纵单元格高度。

How can this be done? Let’s find out.

如何才能做到这一点? 让我们找出答案。

示例应用 (The Sample App)

For simplicity’s sake, our sample app will show a simple custom cell that contains only 1 label. Here’s the screenshot of the sample app:

为简单起见,我们的示例应用程序将显示一个仅包含1个标签的简单自定义单元格。 这是示例应用程序的屏幕截图:

Designing Custom UICollectionViewListCell in Interface Builder
The sample app we going to build
我们将要构建的示例应用程序

Note that I also added a red border to the label so that it is easier for us to observe its height later in this article.

请注意,我还在标签上添加了红色边框,以便我们稍后在本文中更容易观察它的高度。

With all that being said, let’s begin.

综上所述,让我们开始吧。

Note:

注意:

I won’t get into too much detail on the concept behind content view, content configuration, and custom cell. If you are not familiar with that, I highly recommend you to go through my previous article “ UICollectionView List with Custom Cell and Custom Configuration “ before proceeding.

我不会在内容视图,内容配置和自定义单元格后面详细介绍概念。 如果您对此不熟悉,强烈建议您先阅读上一篇文章“ 具有自定义单元格和自定义配置的UICollectionView列表 ”,然后再继续。

实施自定义内容视图和内容配置 (Implementing Custom Content View and Content Configuration)

First thing first, let’s implement the custom content view and content configuration. Let’s name both of them SFSymbolNameContentView and SFSymbolNameContentConfiguration.

首先,让我们实现自定义内容视图和内容配置。 让我们将它们都命名为SFSymbolNameContentViewSFSymbolNameContentConfiguration

Here’s how we will implement the SFSymbolNameContentView class:

这是我们将如何实现SFSymbolNameContentView类的方法:

class SFSymbolNameContentView: UIView, UIContentView {
    
    // 1
    // IBOutlet to connect to interface builder
    @IBOutlet var containerView: UIView!
    @IBOutlet var nameLabel: UILabel!
    
    private var currentConfiguration: SFSymbolNameContentConfiguration!
    var configuration: UIContentConfiguration {
        get {
            currentConfiguration
        }
        set {
            // Make sure the given configuration is of type SFSymbolNameContentConfiguration
            guard let newConfiguration = newValue as? SFSymbolNameContentConfiguration else {
                return
            }
            
            // Set name to label
            apply(configuration: newConfiguration)
        }
    }
    
    init(configuration: SFSymbolNameContentConfiguration) {
        super.init(frame: .zero)
        
        // 2
        // Load SFSymbolNameContentView.xib
        loadNib()
        
        // Set name to label
        apply(configuration: configuration)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}


private extension SFSymbolNameContentView {
    
    private func loadNib() {
        
        // 3
        // Load SFSymbolNameContentView.xib by making self as owner of SFSymbolNameContentView.xib
        Bundle.main.loadNibNamed("\(SFSymbolNameContentView.self)", owner: self, options: nil)
        
        // 4
        // Add SFSymbolNameContentView as subview and make it cover the entire content view
        addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            containerView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0),
            containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0),
            containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0.0),
            containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0.0),
        ])
        
        // 5
        // Add border & rounded corner to name label
        nameLabel.layer.borderWidth = 1.5
        nameLabel.layer.borderColor = UIColor.systemPink.cgColor
        nameLabel.layer.cornerRadius = 5.0
    }
    
    private func apply(configuration: SFSymbolNameContentConfiguration) {
    
        // Only apply configuration if new configuration and current configuration are not the same
        guard currentConfiguration != configuration else {
            return
        }
        
        // Replace current configuration with new configuration
        currentConfiguration = configuration
        
        // Set name to label
        nameLabel.text = configuration.name
    }
}

From the above code, there are several important parts that we must take note:

从上面的代码中,我们必须注意几个重要部分:

  1. Define IBOutlet for the label and container view. Note that the container view will be our interface builder canvas, we will be working on that in just a moment.

    为标签和容器视图定义IBOutlet 。 请注意,容器视图将是我们的界面构建器画布,我们将在稍后对此进行处理。

  2. Call the loadNib() function during initialization to configure SFSymbolNameContentView accordingly.

    在初始化期间调用loadNib()函数以相应地配置SFSymbolNameContentView

  3. Within the loadNib() function, load a nib file named SFSymbolNameContentView.xib and make self as the owner of the nib file.

    loadNib()函数中,加载一个名为SFSymbolNameContentView.xib的笔尖文件,并使其self成为该笔尖文件的所有者。

  4. Make the container view take up the entire content view using auto layout constraints.

    使用自动布局约束使容器视图占据整个内容视图。
  5. Add a red color border and rounded corners to the label.

    在标签上添加红色边框和圆角。

Next up, we will work on the implementation of SFSymbolNameContentConfiguration class. The implementation is pretty straightforward, we just need to make sure the makeContentView()is returning an instance of SFSymbolNameContentView.

接下来,我们将研究SFSymbolNameContentConfiguration类的实现。 该实现非常简单,我们只需要确保makeContentView()返回SFSymbolNameContentView的实例SFSymbolNameContentView

struct SFSymbolNameContentConfiguration: UIContentConfiguration, Hashable {


    var name: String?
    
    func makeContentView() -> UIView & UIContentView {
        // Initialize an instance of SFSymbolNameContentView
        return SFSymbolNameContentView(configuration: self)
    }
    
    func updated(for state: UIConfigurationState) -> Self {
        return self
    }
}

在Interface Builder中创建自定义UIView (Creating Custom UIView in Interface Builder)

With both content view and content configuration classes in place, let’s switch our focus to the interface builder. Here’s what we are going to do:

有了内容视图和内容配置类,让我们将重点转移到界面构建器上。 这是我们要做的:

  1. Create a new user interface view.

    创建一个新的用户界面视图。
  2. Set the file’s owner’s class.

    设置文件所有者的类。
  3. Resize the custom view.

    调整自定义视图的大小。
  4. Add a label and configure auto layout.

    添加标签并配置自动布局。
  5. Connecting all IBOutlets.

    连接所有IBOutlet

1.创建一个新的用户界面视图 (1. Create a New User Interface View)

In Xcode, press ⌘N (cmd + N) to add a new file to your project. In the add new file dialog, select “View” under the user interface section, click “Next” and name the nib file SFSymbolNameContentView.

在Xcode中,按⌘N(cmd + N)将新文件添加到项目中。 在“添加新文件”对话框中,在用户界面部分下选择“查看”,单击“下一步”,然后将笔尖文件命名为SFSymbolNameContentView

Add new nib file in Xcode
Add new nib file in Xcode
在Xcode中添加新的nib文件

2.设置文件所有者的类 (2. Set the File’s Owner’s Class)

Next, we must let the interface builder know that the nib file we just created is owned by the SFSymbolNameContentView class.

接下来,我们必须让接口构建器知道我们刚刚创建的nib文件由SFSymbolNameContentView类拥有。

Inside the interface builder left panel, click on “File’s Owner” and then within the attribute inspector, set the file’s owner’s class to SFSymbolNameContentView.

在界面构建器左面板内,单击“文件的所有者”,然后在属性检查器中,将文件的所有者的类设置为SFSymbolNameContentView

Setting file’s owner in Xcode interface builder
Setting file’s owner
设置文件的所有者

3.调整自定义视图的大小 (3. Resize the Custom View)

By default, the interface builder will create a custom view following the iPhone form factor. However, this is not what we want, we need a view much smaller than that.

默认情况下,界面构建器将按照iPhone尺寸创建一个自定义视图。 但是,这不是我们想要的,我们需要的视图要小得多。

Go ahead and open the view’s attribute inspector and change the size simulated metrics to “ freeform “.

继续并打开视图的属性检查器,并将大小模拟指标更改为“ 自由格式 ”。

Setting view’s size simulated metrics size to freeform in Xcode
Setting view’s size simulated metrics size to freeform
将视图的大小模拟指标大小设置为自由格式

After that, open the size inspector and set the view height to 150pt.

之后,打开尺寸检查器并将视图高度设置为150pt

Setting view’s height in Xcode
Setting view’s height to 150pt
将视图的高度设置为150pt

4.添加标签并配置自动布局 (4. Add a Label and Configure Auto Layout)

Now drag a UILabel into the view and set its top, bottom, leading, and trailing auto layout constraint according to the image below.

现在,将UILabel拖动到视图中,并根据下图设置其顶部,底部,前导和尾随自动布局约束。

Setting top, bottom, leading and, trailing constrains in Xcode
Setting label’s top, bottom, leading and, trailing constrains
设置标签的顶部,底部,前导和尾随约束
Auto layout constraints in Xcode
The label’s auto layout constraints
标签的自动布局约束

This will make the UILabel take up the entire view.

这将使UILabel占据整个视图。

5.连接所有IBOutlets (5. Connecting All IBOutlets)

Lastly, connect the containerView and nameLabel IBOutlet to the interface builder as shown in the image below:

最后,将containerViewnameLabel IBOutlet连接到界面生成器,如下图所示:

Connecting IBOutlets in Xcode interface builder
Connecting the IBOutlets
连接IBOutlets

With that, we have successfully connected the SFSymbolNameContentView class to the interface builder. Let's go ahead and run your sample app to see everything in action.

这样,我们就成功地将SFSymbolNameContentView类连接到了界面构建器。 让我们继续运行您的示例应用程序,以查看运行中的所有内容。

But wait! The cell height is different from what we expected! 😨

可是等等! 单元高度与我们预期的不同! 😨

Unexpected cell height in UICollectionView list
The unexpected cell height
意外的细胞高度

In the next section, let’s find out what causes this problem and how we can fix it.

在下一节中,让我们找出导致此问题的原因以及如何解决它。

处理单元格高度 (Dealing with Cell Height)

The reason behind the unexpected cell height is because UICollectionViewListCell is self-sizing by default.

意外的单元格高度背后的原因是因为UICollectionViewListCell默认情况下是自动调整大小的。

Since we do not specify the height constraint of the nameLabel and we only configure the nameLabel to take up the entire containerView, the collection view will automatically resize the cells based on the nameLabel 's content.

由于我们未指定nameLabel的高度约束,而仅将nameLabel配置为占用整个containerView ,因此收集视图将根据nameLabel的内容自动调整单元格的大小。

The solution to fix the problem is pretty straightforward. We just need to add a height constraint to the nameLabel so that the auto layout engine will take into account the nameLabel 's height when calculating the cell's height.

解决该问题的解决方案非常简单。 我们只需nameLabel添加一个高度约束,以便自动布局引擎在计算单元格的高度时将考虑nameLabel的高度。

Adding height constraint to label
nameLabel nameLabel添加高度约束

If you run your sample app now, you should see that the cell has been resized correctly. Unfortunately, by adding a height constraint to the nameLabel has caused unsatisfiable constraints within the cell.

如果现在运行示例应用程序,则应该看到该单元格已正确调整大小。 不幸的是,通过将高度限制添加到nameLabel导致了单元内nameLabel 满足的限制

Based on the Xcode console output, it seems like the nameLabel 's height constraint is conflicting with the content view's height constraint.

根据Xcode控制台的输出,似乎nameLabel的高度约束与内容视图的高度约束冲突。

Unsatisfiable constraints error log in Xcode console
Unsatisfiable constraints error log in Xcode console
Xcode控制台中无法满足的约束错误日志

“But we never set any height constraint on the content view!” you might say.

“但是我们从未在内容视图上设置任何高度限制!” 你可能会说。

I am not 100% sure on why there is a height constraint being set on the content view, I suspect UIKit is giving the content view a default 44pt height constraints before calculating its actual height.

我不确定为什么在内容视图上设置了高度限制,我不确定100%,我怀疑UIKit在计算实际高度之前会给内容视图默认的44pt高度限制。

Since we set our nameLabel to have 126pt height, but the content view can only have 44pt height, it is not possible to simultaneously satisfy both of these constraints, thus causing the unsatisfiable constraints issue.

由于我们将nameLabel设置为具有126pt的高度,但是内容视图只能具有44pt的高度,因此无法同时满足这两个约束,从而导致无法满足的约束问题。

To resolve this issue, we can reduce the nameLabel's height constraint priority to 999 so that the auto layout engine can temporarily ignore this constraint.

要解决此问题,我们可以将nameLabel的高度约束优先级降低为999,以便自动布局引擎可以暂时忽略此约束。

Setting constraint priority in Xcode interface builder
nameLabel height constraint priority to 999 nameLabel高度限制优先级设置为999

This article discusses in great details the solution we use to resolve the unsatisfiable constraints issue. Feel free to check it out if you want to know more.

本文非常详细地讨论了我们用来解决无法满足的约束问题的解决方案。 如果您想了解更多信息,请随时查看。

With that, we have successfully designed a custom UICollectionViewListCell using an interface builder. 🥳

这样,我们已经使用界面构建器成功设计了一个自定义UICollectionViewListCell 。 🥳

You can get the full sample project here.

您可以在此处获得完整的示例项目。

一个常见的例外 (One Common Exception)

When you design a custom UICollectionViewListCell in an interface builder, one most common exception you might encounter is the ' NSInternalInconsistencyException '.

在界面生成器中设计自定义UICollectionViewListCell时,您可能会遇到的最常见的异常是“ NSInternalInconsistencyException ”。

The exception message looks something like this in the Xcode console:

在Xcode控制台中,异常消息如下所示:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Rounding frame ({{20, -8.9884656743115785e+307}, {374, 1.7976931348623157e+308}}) from preferred layout attributes resulted in a frame with one or more invalid members ({{20, -8.9884656743115785e+307}, {374, inf}}). Layout attributes: <UICollectionViewLayoutAttributes: 0x7fd0d2043130> index path: (<NSIndexPath: 0x947fdb8ab9f8e625> {length = 2, path = 0 - 0}); frame = (20 -8.98847e+307; 374 1.79769e+308); zIndex = 10; View: <Swift_Senpai_UICollectionView_List.SFSymbolNameListCell: 0x7fd0cfc21470; baseClass = UICollectionViewListCell; frame = (20 17.5; 374 44); clipsToBounds = YES; layer = <CALayer: 0x600002768000>>'

This kind of exception is due to the auto layout engine not having sufficient information to determine the cell’s height. It is usually caused by missing vertical spacing constraints and missing height constraints within the content view. Therefore, when it happens, make sure to recheck the auto layout configurations of your content view’s content.

这种例外是由于自动布局引擎没有足够的信息来确定单元的高度。 这通常是由于内容视图中缺少垂直间距约束高度约束而导致的。 因此,当发生这种情况时,请确保重新检查内容视图内容的自动布局配置。

结语 (Wrapping Up)

The concept presented in the “ Creating Custom UIView in Interface Builder” section is basically connecting a UIView subclass to the interface builder. Therefore, this technique is not limited to just for creating a custom UICollectionViewListCell.

在Interface Builder中创建自定义UIView ”一节中介绍的概念基本上是将UIView子类连接到Interface Builder。 因此,此技术不仅限于创建自定义UICollectionViewListCell

You can definitely use the same technique whenever you want to design a custom UIView using interface builder. If you are like me, prefer to use interface builder for UI creation, then I am sure that you will find this technique quite handy.

每当您要使用界面生成器设计自定义UIView时,都可以绝对使用相同的技术。 如果您像我一样,宁愿使用界面生成器来创建UI,那么我相信您会发现此技术非常方便。

If you like this article, feel free to follow me on Twitter.

如果您喜欢这篇文章,请随时在Twitter上关注我。

Thanks for reading and happy designing. 👨🏻‍💻

感谢您的阅读和愉快的设计。 ‍💻

进一步阅读 (Further Readings)

翻译自: https://levelup.gitconnected.com/designing-custom-uicollectionviewlistcell-in-interface-builder-a2f72c2477b3

qt自定义界面生成器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值