如何在不闪烁视图的情况下删除和添加 ArrangedSubview?
How to remove and addArrangedSubvew without view blinking?
我有一些视图来自一个堆栈视图。每 n
秒我都应该用来自服务器的数据更新这个视图。当新数据到来时,我清理 stackView(使用其子项的 removeFromSuperView 方法)并再次添加 arrangedSubviews 以更新 UI。有时,服务器发送与旧数据相同的数据。但是做这个更新操作,我的看法有点不寒而栗。每次我清理视图并将视图添加到我的 stackview 时,它有点拖拉和颤抖。当然,如果 oldData != newData,我只能更新我的 UI。但是这个比赛很难,很难找到正确的。那么,如何在不颤抖和不眨眼的情况下用新数据更新堆栈视图?
这是我的代码:
func configure(_ items: [Item]) {
stackView.arrangedSubviews.forEach { [=10=].removeFromSuperview() }
items.forEach {
let someView = SomeView()
someView.configure([=10=])
stackView.addArrangedSubview(someView)
}
}
而不是删除/重新添加视图到堆栈视图...
- 仅当 Item 多于当前排列的子视图时才添加新视图
- 设置数据-
configure()
-每个排列的子视图
- 如果超出需要,则隐藏现有的排列子视图
例如:
func configure(_ items: [Item]) {
// if we have fewer arranged subviews than items
// add new ones
while stackView.arrangedSubviews.count < items.count {
stackView.addArrangedSubview(SomeView())
}
// hide any existing arranged subviews if we have too many
for i in 0..<stackView.arrangedSubviews.count {
stackView.arrangedSubviews[i].isHidden = i >= items.count
}
// update the existing arranged subviews with the new data
for (thisItem, thisView) in zip(items, stackView.arrangedSubviews) {
// unwrap the arranged subview
guard let v = thisView as? SomeView else {
continue
}
v.configure(thisItem)
}
}
这是一个完整的示例 - “项目”的数量及其数据每 1.5 秒更改一次:
具有两个字符串的示例结构项目:
struct Item {
var title: String = ""
var desc: String = ""
}
带有两个标签的示例“SomeView”class:
class SomeView: UIView {
let titleLabel = UILabel()
let descLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
backgroundColor = .systemYellow
titleLabel.font = .boldSystemFont(ofSize: 18.0)
descLabel.font = .italicSystemFont(ofSize: 15.0)
[titleLabel, descLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white // so we can see the label frames at run-time
addSubview(v)
}
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
descLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4.0),
descLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
descLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
descLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
])
}
func configure(_ item: Item) -> Void {
titleLabel.text = item.title
descLabel.text = item.desc
}
}
使用堆栈视图示例“NeoView”class:
class NeoView: UIView {
let stackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 8
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
stackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
])
}
func configure(_ items: [Item]) {
// if we have fewer arranged subviews than items
// add new ones
while stackView.arrangedSubviews.count < items.count {
stackView.addArrangedSubview(SomeView())
}
// hide any existing arranged subviews if we have too many
for i in 0..<stackView.arrangedSubviews.count {
stackView.arrangedSubviews[i].isHidden = i >= items.count
}
// update the existing arranged subviews with the new data
for (thisItem, thisView) in zip(items, stackView.arrangedSubviews) {
// unwrap the arranged subview
guard let v = thisView as? SomeView else {
continue
}
v.configure(thisItem)
}
}
}
示例控制器 class:
class NeoTestViewController: UIViewController {
let neoView: NeoView = {
let v = NeoView()
v.backgroundColor = .systemTeal
return v
}()
var items: [[Item]] = []
var idx: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
neoView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(neoView)
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
neoView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
neoView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
neoView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// no bottom constraint
])
items = sampleData()
var idx: Int = 0
self.neoView.configure(self.items[idx % self.items.count])
// update neoView with a new set of items every 1.5 seconds
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { timer in
idx += 1
self.neoView.configure(self.items[idx % self.items.count])
}
}
func sampleData() -> [[Item]] {
// we'll create 8 sets of [Item] arrays
// each with a different number of Items
let numItems: [Int] = [
4, 2, 6, 3, 7, 8, 3, 5,
]
var items: [[Item]] = []
var i: Int = 1
numItems.forEach { n in
var theseItems: [Item] = []
for j in 1...n {
let thisItem = Item(title: "Set \(i) item \(j) title.", desc: "Set \(i) item \(j) description.")
theseItems.append(thisItem)
}
theseItems[0].title = "Set \(i) has \(n) items."
items.append(theseItems)
i += 1
}
return items
}
}
我有一些视图来自一个堆栈视图。每 n
秒我都应该用来自服务器的数据更新这个视图。当新数据到来时,我清理 stackView(使用其子项的 removeFromSuperView 方法)并再次添加 arrangedSubviews 以更新 UI。有时,服务器发送与旧数据相同的数据。但是做这个更新操作,我的看法有点不寒而栗。每次我清理视图并将视图添加到我的 stackview 时,它有点拖拉和颤抖。当然,如果 oldData != newData,我只能更新我的 UI。但是这个比赛很难,很难找到正确的。那么,如何在不颤抖和不眨眼的情况下用新数据更新堆栈视图?
这是我的代码:
func configure(_ items: [Item]) {
stackView.arrangedSubviews.forEach { [=10=].removeFromSuperview() }
items.forEach {
let someView = SomeView()
someView.configure([=10=])
stackView.addArrangedSubview(someView)
}
}
而不是删除/重新添加视图到堆栈视图...
- 仅当 Item 多于当前排列的子视图时才添加新视图
- 设置数据-
configure()
-每个排列的子视图 - 如果超出需要,则隐藏现有的排列子视图
例如:
func configure(_ items: [Item]) {
// if we have fewer arranged subviews than items
// add new ones
while stackView.arrangedSubviews.count < items.count {
stackView.addArrangedSubview(SomeView())
}
// hide any existing arranged subviews if we have too many
for i in 0..<stackView.arrangedSubviews.count {
stackView.arrangedSubviews[i].isHidden = i >= items.count
}
// update the existing arranged subviews with the new data
for (thisItem, thisView) in zip(items, stackView.arrangedSubviews) {
// unwrap the arranged subview
guard let v = thisView as? SomeView else {
continue
}
v.configure(thisItem)
}
}
这是一个完整的示例 - “项目”的数量及其数据每 1.5 秒更改一次:
具有两个字符串的示例结构项目:
struct Item {
var title: String = ""
var desc: String = ""
}
带有两个标签的示例“SomeView”class:
class SomeView: UIView {
let titleLabel = UILabel()
let descLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
backgroundColor = .systemYellow
titleLabel.font = .boldSystemFont(ofSize: 18.0)
descLabel.font = .italicSystemFont(ofSize: 15.0)
[titleLabel, descLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white // so we can see the label frames at run-time
addSubview(v)
}
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
descLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4.0),
descLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
descLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
descLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
])
}
func configure(_ item: Item) -> Void {
titleLabel.text = item.title
descLabel.text = item.desc
}
}
使用堆栈视图示例“NeoView”class:
class NeoView: UIView {
let stackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 8
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
stackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
])
}
func configure(_ items: [Item]) {
// if we have fewer arranged subviews than items
// add new ones
while stackView.arrangedSubviews.count < items.count {
stackView.addArrangedSubview(SomeView())
}
// hide any existing arranged subviews if we have too many
for i in 0..<stackView.arrangedSubviews.count {
stackView.arrangedSubviews[i].isHidden = i >= items.count
}
// update the existing arranged subviews with the new data
for (thisItem, thisView) in zip(items, stackView.arrangedSubviews) {
// unwrap the arranged subview
guard let v = thisView as? SomeView else {
continue
}
v.configure(thisItem)
}
}
}
示例控制器 class:
class NeoTestViewController: UIViewController {
let neoView: NeoView = {
let v = NeoView()
v.backgroundColor = .systemTeal
return v
}()
var items: [[Item]] = []
var idx: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
neoView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(neoView)
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
neoView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
neoView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
neoView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// no bottom constraint
])
items = sampleData()
var idx: Int = 0
self.neoView.configure(self.items[idx % self.items.count])
// update neoView with a new set of items every 1.5 seconds
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { timer in
idx += 1
self.neoView.configure(self.items[idx % self.items.count])
}
}
func sampleData() -> [[Item]] {
// we'll create 8 sets of [Item] arrays
// each with a different number of Items
let numItems: [Int] = [
4, 2, 6, 3, 7, 8, 3, 5,
]
var items: [[Item]] = []
var i: Int = 1
numItems.forEach { n in
var theseItems: [Item] = []
for j in 1...n {
let thisItem = Item(title: "Set \(i) item \(j) title.", desc: "Set \(i) item \(j) description.")
theseItems.append(thisItem)
}
theseItems[0].title = "Set \(i) has \(n) items."
items.append(theseItems)
i += 1
}
return items
}
}