带有StackView的UITableViewCell以及动态添加的WKWebViews和UIImageViews

问题描述

正如标题所述,我正在尝试显示以下布局:

I'm not able to indent because of that I did a screenshot

如您所见,动态堆栈视图是一个动态添加内容的容器。该内容是可变的,并且取决于运行时间。基本上,它可以是Web视图(内部内容可变),ImageView(高度可变)和视频(此视图具有固定的视图)。

我为CellView配置了自动行高,并以代码和Xcode提供了估计的行高。然后在ViewController方法的tableView_cellForRow上,将单元出队,并使用内容呈现该单元。

在此设置过程中,内容将填充不同的标签和视图,动态容器也将填充。使用以下代码将Web视图添加到stackview中:

    var webView = WKWebView(frame: .zero,configuration: webConfiguration)
    webView.scrollView.isScrollEnabled = false
    webView.navigationDelegate = myNavigationDelegate

    webView = addContentToWebView(content,webView)
    container.addArrangedSubview(webView)

我正在用stackview内的一个webview进行测试,并且行高已经存在问题。

该webview在stackview内正确呈现,但不完全呈现(该webview大于估计的行高)。我使用导航委托来计算添加的Webview的高度,并使用以下代码相应地调整StackContainer的大小:

  webView.evaluateJavaScript("document.readyState",completionHandler: { (complete,error) in
        if complete != nil {
            webView.evaluateJavaScript("document.body.scrollHeight",completionHandler: { (height,error) in
               
                let h = height as! CGFloat
                print("Height 3 is \(h)")
                self.dynamicContainerHeightContraint.constant = h

            })
        }
        
    })

实际上,stackcontainer已调整大小并扩展为与内部Web视图的高度匹配。

但是该行仍保持相同的估计高度,如果webview的高度很大,则所有其他视图都消失(它们被推到行的边界之外。

是否可以告诉行自动调整大小并适应其内容?还是我使用的是错误方法

我想问题是添加到stackview的视图的高度是未知的,但是我期望有一种方法可以告诉行在内部添加了所有必需的东西之后重新计算其高度... >

谢谢。

解决方法

当单元格的内容发生更改时,表视图不会自动重绘其单元格。

由于在呈现单元格之后更改了单元格的dynamicContainerHeightContraint 的常量(您的Web视图的页面加载是异步的),所以该表不会自动更新-你已经看到了。

要解决此问题,您可以在单元格中添加一个“回调”闭包,这将使该单元格告诉控制器重新计算布局。

这是一个简单的示例来演示。

该单元格具有单个标签...它具有一个“标签高度限制”变量,该变量最初将标签的高度设置为30。

对于第三行,我们将设置一个3秒的计时器来模拟Web视图中的延迟页面加载。 3秒后,单元格的代码会将高度常数更改为80。

这是开始的样子:

enter image description here

没有 回调关闭,这是3秒后的外观:

enter image description here

使用 回调关闭,这是3秒后的外观:

enter image description here

这是示例代码。

DelayedCell UITableViewCell类

class DelayedCell: UITableViewCell {
    
    let myLabel = UILabel()
    var heightConstraint: NSLayoutConstraint!
    
    // closure to tell the controller our content changed height
    var callback: (() -> ())?
    
    var timer: Timer?
    
    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 {
        
        contentView.clipsToBounds = true
        
        myLabel.translatesAutoresizingMaskIntoConstraints = false
        
        contentView.addSubview(myLabel)
        
        let g = contentView.layoutMarginsGuide
        
        // we'll change this dynamically
        heightConstraint = myLabel.heightAnchor.constraint(equalToConstant: 30.0)
        
        // use bottom anchor with Prioirty: 999 to avoid auto-layout complaints
        let bc = myLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor)
        bc.priority = UILayoutPriority(rawValue: 999)

        NSLayoutConstraint.activate([
            
            // constrain label to all 4 sides
            myLabel.topAnchor.constraint(equalTo: g.topAnchor),myLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),myLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),// activate bottom and height constraints
            bc,heightConstraint,])
        
    }
    
    func fillData(_ str: String,testTimer: Bool) -> Void {
        
        myLabel.text = str
        
        // so we can see the label frame
        //  green if we're testing the timer in this cell
        //  otherwise yellow
        myLabel.backgroundColor = testTimer ? .green : .yellow
        
        if testTimer {
            // trigger a timer in 3 seconds to change the height of the label
            //  simulating the delayed load of the web view
            timer = Timer.scheduledTimer(timeInterval: 3.0,target: self,selector: #selector(self.heightChanged),userInfo: nil,repeats: false)
        }
    }
    
    @objc func heightChanged() -> Void {
        
        // change the height constraint
        heightConstraint.constant = 80
        
        myLabel.text = "Height changed to 80"

        // run this example first with the next line commented
        // then run it again but un-comment the next line
        
        // tell the controller we need to update
        //callback?()
        
    }
    
    override func willMove(toSuperview newSuperview: UIView?) {
        if newSuperview == nil {
            timer?.invalidate()
        }
    }
    
}

DelayTestTableViewController UITableViewController类

class DelayTestTableViewController: UITableViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(DelayedCell.self,forCellReuseIdentifier: "cell")
    }
    override func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    override func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell",for: indexPath) as! DelayedCell
        
        // we'll test the delayed content height change for row 2
        let bTest = indexPath.row == 2
        cell.fillData("Row \(indexPath.row)",testTimer: bTest)
        
        // set the callback closure
        cell.callback = { [weak tableView] in
            guard let tv = tableView else { return }
            // this will tell the tableView to recalculate row heights
            //  without reloading the cells
            tv.performBatchUpdates(nil,completion: nil)
        }
        
        return cell
    }
    
}

您的代码中,您将在此行之后进行闭包回调:

self.dynamicContainerHeightContraint.constant = h