UITableViewCell 里面有一个 StackView,里面有动态添加的 WKWebViews 和 UIImageViews
Posted
技术标签:
【中文标题】UITableViewCell 里面有一个 StackView,里面有动态添加的 WKWebViews 和 UIImageViews【英文标题】:UITableViewCell with a StackView inside with dynamically added WKWebViews and UIImageViews 【发布时间】:2020-10-15 17:17:18 【问题描述】:正如标题所说,我正在尝试显示以下布局:
如您所见,动态堆栈视图是动态添加内容的容器。该内容是可变的,并且取决于运行时间。基本上,它可以是 webviews(里面有可变内容)、ImageViews(有可变高度)和视频(这个 view 会有一个固定的 view)。
我为 CellView 配置了自动行高,并在代码和 Xcode 中提供了估计的行高。然后在 ViewController 的方法中的 tableView_cellForRow 上,将单元格出列,并用内容渲染单元格。
在此设置过程中,不同的标签和视图都填充了内容,动态容器也一样。使用以下代码将 webview 添加到 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 比估计的 rowheight 大)。我使用导航委托来计算添加的 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 会调整大小并展开以匹配内部 webview 的高度。
但是该行保持相同的估计高度,如果 webview 的高度非常大,那么所有其他视图都会消失(它们被推到行的边界之外。
有没有办法告诉行自动调整大小并适应其内容?还是我使用了错误的方法?
我想问题是添加到 stackview 的视图的高度事先不知道,但我期待有一种方法可以告诉行在添加所有需要的东西后重新计算它的高度......
提前谢谢你。
【问题讨论】:
【参考方案1】:当单元格的内容发生变化时,表格视图不会自动重绘其单元格。
由于您正在更改单元格的 dynamicContainerHeightContraint
的常量在单元格已呈现(您的 Web 视图的页面加载是异步的),表格不会自动更新 - 因为您见过。
要解决此问题,您可以向单元格添加“回调”闭包,这将使单元格告诉控制器重新计算布局。
这里是一个简单的例子来演示。
单元格有一个标签...它有一个“标签高度约束”变量,最初将标签的高度设置为 30。
对于第 3 行,我们将设置一个 3 秒计时器来模拟您的网络视图中的延迟页面加载。 3 秒后,单元格的代码会将高度常数更改为 80。
下面是开始的样子:
没有回调闭包,下面是 3 秒后的样子:
使用回调闭包,3秒后的效果如下:
这是示例代码。
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
【讨论】:
以上是关于UITableViewCell 里面有一个 StackView,里面有动态添加的 WKWebViews 和 UIImageViews的主要内容,如果未能解决你的问题,请参考以下文章
UITableViewCell里面的UIPageViewController