首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >将项目在多行的水平UIStackView中包装

将项目在多行的水平UIStackView中包装
EN

Stack Overflow用户
提问于 2020-03-07 20:56:23
回答 1查看 5K关注 0票数 10

我正在开发一个iOS应用程序,它显示一个项目的UITableView。每行将显示一个具有不同宽度的标记列表(由标记的文本长度给出)。我可以将所有的标记放置在一个水平的UIStackView中,但是我希望它们以多行方式包装,而不是单一的滚动行。基本上,我对类似于FlexBox的flex-wrap属性的功能感兴趣。

我附上了一张图片作为参考。

有什么办法可以做到吗?

EN

回答 1

Stack Overflow用户

发布于 2020-03-08 14:34:40

有许多不同的方法来处理这个问题。

一种方法是使用堆栈视图来而不是

通过标签将标签添加到“容器”视图( y = 0

  • loop )中,从x = 0和开始,计算一个新的x值(标签宽度+标签之间的期望间距)
  • (如果新的x将超过容器边缘,重置x = 0并将所需的高度添加到y以“移动到下一行”H 216H 117在标签布局后设置容器视图的高度H 218f 219

下面是一个简单的例子:

代码语言:javascript
运行
复制
class TagLabelsViewController: UIViewController {
    
    let containerView: UIView = {
        let v = UIView()
        return v
    }()
    
    let tagNames: [String] = [
        "First Tag",
        "Second",
        "Third Tag",
        "Fourth",
        "The Fifth Tag",
        "Sixth",
        "Seventh",
        "Tag Eight",
        "Here are some Letter Tags",
        "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
        "Nine",
        "Ten",
        "Eleven",
        "Tag Twelve",
        "Tag 13",
        "Fourteen",
        "Fifteen",
        "Sixteen",
        "Seventeen",
        "Eightteen",
        "Nineteen",
        "Last Tag",
    ]
    
    var tagLabels = [UILabel]()
    
    let tagHeight:CGFloat = 30
    let tagPadding: CGFloat = 16
    let tagSpacingX: CGFloat = 8
    let tagSpacingY: CGFloat = 8
    
    // container view height will be modified when laying out subviews
    var containerHeightConstraint: NSLayoutConstraint = NSLayoutConstraint()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // add the container view
        view.addSubview(containerView)
        
        // give it a background color so we can see it
        containerView.backgroundColor = .yellow
        
        // use autolayout
        containerView.translatesAutoresizingMaskIntoConstraints = false
        
        // initialize height constraint - actual height will be set later
        containerHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: 10.0)
        
        // constrain container safe-area top / leading / trailing to view with 20-pts padding
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            containerHeightConstraint,
        ])
        
        // add the buttons to the scroll view
        addTagLabels()
        
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        // call this here, after views have been laid-out
        // this will also be called when the size changes, such as device rotation,
        // so the buttons will "re-layout"
        displayTagLabels()
        
    }
    
    func addTagLabels() -> Void {
        
        for j in 0..<self.tagNames.count {
            
            // create a new label
            let newLabel = UILabel()
            
            // set its properties (title, colors, corners, etc)
            newLabel.text = tagNames[j]
            newLabel.textAlignment = .center
            newLabel.backgroundColor = UIColor.cyan
            newLabel.layer.masksToBounds = true
            newLabel.layer.cornerRadius = 8
            newLabel.layer.borderColor = UIColor.red.cgColor
            newLabel.layer.borderWidth = 1

            // set its frame width and height
            newLabel.frame.size.width = newLabel.intrinsicContentSize.width + tagPadding
            newLabel.frame.size.height = tagHeight
            
            // add it to the scroll view
            containerView.addSubview(newLabel)
            
            // append it to tagLabels array
            tagLabels.append(newLabel)
            
        }
        
    }
    
    func displayTagLabels() {
        
        let containerWidth = containerView.frame.size.width
        
        var currentOriginX: CGFloat = 0
        var currentOriginY: CGFloat = 0
        
        // for each label in the array
        tagLabels.forEach { label in
            
            // if current X + label width will be greater than container view width
            //  "move to next row"
            if currentOriginX + label.frame.width > containerWidth {
                currentOriginX = 0
                currentOriginY += tagHeight + tagSpacingY
            }
            
            // set the btn frame origin
            label.frame.origin.x = currentOriginX
            label.frame.origin.y = currentOriginY
            
            // increment current X by btn width + spacing
            currentOriginX += label.frame.width + tagSpacingX
            
        }
        
        // update container view height
        containerHeightConstraint.constant = currentOriginY + tagHeight
        
    }
    
}

结果:

这是相当直截了当的,随着代码中的注释,您应该能够使它适应您的需要。

如果您想要一个“预构建”解决方案,可能需要更多功能,请搜索

代码语言:javascript
运行
复制
swift left aligned tags view

想出了很多火柴。这个(我和它无关)看起来很有趣:https://github.com/ElaWorkshop/TagListView

编辑

在表视图单元格中使用此概念与将其用作视图控制器中的视图没有多大区别。

第一步,让我们创建一个自定义UIView子类来处理所有布局逻辑:

代码语言:javascript
运行
复制
class TagLabelsView: UIView {
    
    var tagNames: [String] = [] {
        didSet {
            addTagLabels()
        }
    }
    
    let tagHeight:CGFloat = 30
    let tagPadding: CGFloat = 16
    let tagSpacingX: CGFloat = 8
    let tagSpacingY: CGFloat = 8

    var intrinsicHeight: CGFloat = 0
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() -> Void {
    }

    func addTagLabels() -> Void {
        
        // if we already have tag labels (or buttons, etc)
        //  remove any excess (e.g. we had 10 tags, new set is only 7)
        while self.subviews.count > tagNames.count {
            self.subviews[0].removeFromSuperview()
        }
        
        // if we don't have enough labels, create and add as needed
        while self.subviews.count < tagNames.count {

            // create a new label
            let newLabel = UILabel()
            
            // set its properties (title, colors, corners, etc)
            newLabel.textAlignment = .center
            newLabel.backgroundColor = UIColor.cyan
            newLabel.layer.masksToBounds = true
            newLabel.layer.cornerRadius = 8
            newLabel.layer.borderColor = UIColor.red.cgColor
            newLabel.layer.borderWidth = 1

            addSubview(newLabel)
            
        }

        // now loop through labels and set text and size
        for (str, v) in zip(tagNames, self.subviews) {
            guard let label = v as? UILabel else {
                fatalError("non-UILabel subview found!")
            }
            label.text = str
            label.frame.size.width = label.intrinsicContentSize.width + tagPadding
            label.frame.size.height = tagHeight
        }

    }
    
    func displayTagLabels() {
        
        var currentOriginX: CGFloat = 0
        var currentOriginY: CGFloat = 0

        // for each label in the array
        self.subviews.forEach { v in
            
            guard let label = v as? UILabel else {
                fatalError("non-UILabel subview found!")
            }

            // if current X + label width will be greater than container view width
            //  "move to next row"
            if currentOriginX + label.frame.width > bounds.width {
                currentOriginX = 0
                currentOriginY += tagHeight + tagSpacingY
            }
            
            // set the btn frame origin
            label.frame.origin.x = currentOriginX
            label.frame.origin.y = currentOriginY
            
            // increment current X by btn width + spacing
            currentOriginX += label.frame.width + tagSpacingX
            
        }
        
        // update intrinsic height
        intrinsicHeight = currentOriginY + tagHeight
        invalidateIntrinsicContentSize()
        
    }

    // allow this view to set its own intrinsic height
    override var intrinsicContentSize: CGSize {
        var sz = super.intrinsicContentSize
        sz.height = intrinsicHeight
        return sz
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        displayTagLabels()
    }
    
}

我们可以在一个单元格内使用它--或者,作为一个“常规的旧子视图”--像这样:

代码语言:javascript
运行
复制
let tagsView = TagLabelsView()
let tags: [String] = ["One", "Two", "Three", "etc..."]
tagsView.tagNames = tags

下面是一个使用自定义TagLabelsView的完整示例

代码语言:javascript
运行
复制
class PlainTagLabelsViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let tagsView = TagLabelsView()
        
        // add the tags view
        view.addSubview(tagsView)
        
        // use autolayout
        tagsView.translatesAutoresizingMaskIntoConstraints = false
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            // constrain to safe-area top / leading / trailing to view with 20-pts padding
            tagsView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            tagsView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            tagsView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
        ])

        // some sample "tags" from Stack Overflow
        let tags: [String] = [
            "asp.net-core",
            "asp.net-mvc",
            "asp.net",
            "azure",
            "bash",
            "c",
            "c#",
            "c++",
            "class",
            "codeigniter",
            "cordova",
            "css",
            "csv",
            "dart",
            "database",
            "dataframe",
        ]

        tagsView.tagNames = tags
        
        // give the tags view a background color so we can see it
        tagsView.backgroundColor = .yellow
    }
    
}

为了在表视图单元格中使用它,我们创建了一个单元格类,它使用我们的TagLabelsView作为子视图:

代码语言:javascript
运行
复制
class TagsCell: UITableViewCell {

    let tagsView: TagLabelsView = {
        let v = TagLabelsView()
        return v
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() -> Void {
        
        // add the container view
        contentView.addSubview(tagsView)
        
        // give it a background color so we can see it
        tagsView.backgroundColor = .yellow
        
        // use autolayout
        tagsView.translatesAutoresizingMaskIntoConstraints = false
        
        // constrain tagsView top / leading / trailing / bottom to
        //  contentView Layout Margins Guide
        let g = contentView.layoutMarginsGuide
        
        NSLayoutConstraint.activate([
            tagsView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            tagsView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            tagsView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            tagsView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
        ])

    }

    func fillData(_ tagNames: [String]) -> Void {
        tagsView.tagNames = tagNames
    }
    
    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        //force layout of all subviews including RectsView, which
        //updates RectsView's intrinsic height, and thus height of a cell
        self.setNeedsLayout()
        self.layoutIfNeeded()

        //now intrinsic height is correct, so we can call super method
        return super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
    }

}

和一个包含几组“标记”的表视图的示例视图控制器:

代码语言:javascript
运行
复制
class TagLabelsViewController: UIViewController {

    var myData: [[String]] = []
    
    let tableView: UITableView = {
        let v = UITableView()
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // add the table view
        view.addSubview(tableView)
        
        // use autolayout
        tableView.translatesAutoresizingMaskIntoConstraints = false
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            // constrain table view safe-area top / leading / trailing / bottom to view with 20-pts padding
            tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
        ])
        
        tableView.register(TagsCell.self, forCellReuseIdentifier: "c")
        tableView.dataSource = self
        tableView.delegate = self

        // get some sample tag data
        myData = SampleTags().samples()
    }

}

extension TagLabelsViewController: UITableViewDataSource, UITableViewDelegate {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! TagsCell
        c.fillData(myData[indexPath.row])
        return c
    }
}

class SampleTags: NSData {
    func samples() -> [[String]] {

        let tmp: [[String]] = [
            [
                ".htaccess",
                ".net",
                "ajax",
                "algorithm",
            ],
            [
                "amazon-web-services",
                "android-layout",
                "android-studio",
                "android",
                "angular",
                "angularjs",
                "apache-spark",
            ],
            [
                "apache",
                "api",
                "arrays",
            ],
            [
                "asp.net-core",
                "asp.net-mvc",
                "asp.net",
                "azure",
                "bash",
                "c",
                "c#",
                "c++",
                "class",
                "codeigniter",
                "cordova",
                "css",
                "csv",
                "dart",
                "database",
                "dataframe",
            ],
            [
                "date",
                "datetime",
                "dictionary",
                "django",
                "docker",
            ],
            [
                "eclipse",
                "email",
                "entity-framework",
                "excel",
                "express",
                "facebook",
            ],
            [
                "file",
                "firebase",
                "flutter",
                "for-loop",
                "forms",
                "function",
                "git",
                "go",
                "google-chrome",
                "google-maps",
                "hibernate",
                "html",
                "http",
            ],
            [
                "image",
                "ios",
                "iphone",
                "java",
                "javascript",
                "jquery",
                "json",
                "kotlin",
                "laravel",
                "linq",
                "linux",
            ],
            [
                "list",
                "loops",
                "macos",
                "matlab",
                "matplotlib",
                "maven",
                "mongodb",
                "multithreading",
                "mysql",
                "node.js",
            ],
            [
                "numpy",
                "object",
                "objective-c",
                "oop",
                "opencv",
                "oracle",
                "pandas",
                "performance",
                "perl",
                "php",
                "postgresql",
                "powershell",
                "python-2.7",
                "python-3.x",
                "python",
            ],
            [
                "qt",
                "r",
                "react-native",
                "reactjs",
                "regex",
                "rest",
                "ruby-on-rails-3",
                "ruby-on-rails",
                "ruby",
                "scala",
                "selenium",
                "shell",
                "sockets",
                "sorting",
                "spring-boot",
                "spring-mvc",
                "spring",
                "sql-server",
                "sql",
            ],
            [
                "sqlite",
                "string",
                "swift",
            ],
            [
                "swing",
                "symfony",
                "tensorflow",
                "tsql",
                "twitter-bootstrap",
                "typescript",
                "uitableview",
                "unit-testing",
                "unity3d",
                "validation",
                "vb.net",
                "vba",
                "visual-studio",
                "vue.js",
                "web-services",
                "windows",
                "winforms",
                "wordpress",
                "wpf",
                "xaml",
                "xcode",
                "xml",
            ],
        ]
        
        return tmp
    }
}

样本输出(iPhone 13 Pro Max):

票数 17
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60582129

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档