UITableViewCell,里面有一个 StackView,动态添加了 WKWebViews 和 UIImageViews
UITableViewCell with a StackView inside with dynamically added WKWebViews and UIImageViews
正如标题所说,我正在尝试显示以下布局:
如您所见,动态堆栈视图是一个动态添加内容的容器。此内容是可变的,并在 运行 时间决定。基本上,它可以是 webviews(内部内容可变)、ImageViews(可变高度)和视频(此视图将具有固定视图)。
我为 CellView 配置了自动行高,并在代码和 Xcode 中提供了估计的行高。然后在 tableView_cellForRow 上 ViewController 的方法中,单元格被出列并且单元格被渲染为内容。
在此设置过程中,不同的标签和视图都填充了内容,动态容器也是如此。使用以下代码将 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 比估计的行高大)。我使用导航委托来计算添加的 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 的高度很大,那么所有其他视图都会消失(它们被推到行的边界之外。
有没有办法让行自动调整大小并适应其内容?或者也许我使用了错误的方法?
我想问题是事先不知道添加到堆栈视图的视图的高度,但我期待一种方法来告诉行在添加所有需要的东西后重新计算它的高度...
提前致谢。
Table 视图不会在单元格内容更改时自动重绘它们的单元格。
由于您要在 dynamicContainerHeightContraint
单元格呈现后更改单元格的常量(您的 Web 视图的页面加载是异步的),table 不会 auto-update -- 如您所见。
要解决此问题,您可以向单元格添加一个“回调”闭包,这将使单元格告诉控制器重新计算布局。
这里有一个简单的例子来演示。
该单元格只有一个标签...它有一个“标签高度约束”变量,最初将标签的高度设置为 30。
对于第 3 行,我们将设置一个 3 秒计时器来模拟 Web 视图中的延迟页面加载。 3 秒后,单元格的代码会将高度常量更改为 80。
这是开始的样子:
没有回调闭包,3秒后的样子:
使用 回调闭包,这是 3 秒后的样子:
这是示例代码。
DelayedCell UITableViewCell class
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()
}
}
}
延迟测试TableViewController UITableViewController class
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
正如标题所说,我正在尝试显示以下布局:
如您所见,动态堆栈视图是一个动态添加内容的容器。此内容是可变的,并在 运行 时间决定。基本上,它可以是 webviews(内部内容可变)、ImageViews(可变高度)和视频(此视图将具有固定视图)。
我为 CellView 配置了自动行高,并在代码和 Xcode 中提供了估计的行高。然后在 tableView_cellForRow 上 ViewController 的方法中,单元格被出列并且单元格被渲染为内容。
在此设置过程中,不同的标签和视图都填充了内容,动态容器也是如此。使用以下代码将 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 比估计的行高大)。我使用导航委托来计算添加的 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 的高度很大,那么所有其他视图都会消失(它们被推到行的边界之外。
有没有办法让行自动调整大小并适应其内容?或者也许我使用了错误的方法?
我想问题是事先不知道添加到堆栈视图的视图的高度,但我期待一种方法来告诉行在添加所有需要的东西后重新计算它的高度...
提前致谢。
Table 视图不会在单元格内容更改时自动重绘它们的单元格。
由于您要在 dynamicContainerHeightContraint
单元格呈现后更改单元格的常量(您的 Web 视图的页面加载是异步的),table 不会 auto-update -- 如您所见。
要解决此问题,您可以向单元格添加一个“回调”闭包,这将使单元格告诉控制器重新计算布局。
这里有一个简单的例子来演示。
该单元格只有一个标签...它有一个“标签高度约束”变量,最初将标签的高度设置为 30。
对于第 3 行,我们将设置一个 3 秒计时器来模拟 Web 视图中的延迟页面加载。 3 秒后,单元格的代码会将高度常量更改为 80。
这是开始的样子:
没有回调闭包,3秒后的样子:
使用 回调闭包,这是 3 秒后的样子:
这是示例代码。
DelayedCell UITableViewCell class
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()
}
}
}
延迟测试TableViewController UITableViewController class
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