Anonymous
В SwiftUI метод представления OnDisappear не запускается в первый раз, когда различаемый источник данных применяется к U
Сообщение
Anonymous » 10 янв 2025, 07:50
Я работаю над приложением для iOS, используя как UITableViewDiffableDataSource, так и SwiftUI, и столкнулся с двумя отдельными, но загадочными проблемами:
UITableViewDiffableDataSource не использует повторно ячейки при первом применении после первоначального снимка. После первого применения он работает так, как ожидалось, со второго раза.
SwiftUI View внутри UITableViewCell onDisappear Не запускает первые изменения снимка после первоначального снимка. После этого onDispear вызывает, как и ожидалось.
С обычным UITableView он работает нормально.
Вызывает проблему — из-за этого проигрыватель и ячейки сильно сохраняют память.
Пример кода для воспроизведения с использованием diffable (DiffableTableViewExampleViewController) и нормальной работы без diffable (RegularTableViewExampleViewController)
Код: Выделить всё
import UIKit
enum TableSection: Hashable {
case featured
case regular
}
struct FeaturedItem: Hashable {
let id: UUID
let title: String
let subtitle: String
}
struct RegularItem: Hashable {
let id: UUID
let description: String
}
class DiffableTableViewExampleViewController: UIViewController {
private var tableView: UITableView!
private var dataSource: UITableViewDiffableDataSource!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupTableView()
configureDataSource()
applyInitialSnapshot()
}
// Update the setupTableView method to use custom cells
private func setupTableView() {
tableView = UITableView(frame: view.bounds, style: .insetGrouped)
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableView.register(FeaturedCell.self, forCellReuseIdentifier: "FeaturedCell")
tableView.register(RegularCell.self, forCellReuseIdentifier: "RegularCell")
view.addSubview(tableView)
navigationItem.rightBarButtonItems = [
UIBarButtonItem(
title: "Reload",
style: .plain,
target: self,
action: #selector(reloadSnapshot)
),
UIBarButtonItem(
title: "Regular View",
style: .plain,
target: self,
action: #selector(navigateToRegularView)
)
]
}
@objc private func navigateToRegularView() {
let regularVC = RegularTableViewExampleViewController()
navigationController?.pushViewController(regularVC, animated: true)
}
@objc private func reloadSnapshot() {
// deleteSections()
self.appendNewItems()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// self.applyInitialSnapshot()
}
//appendNewItems()
}
func deleteSections() {
var snapshot = dataSource.snapshot()
snapshot.deleteSections([.regular, .featured])
dataSource.apply(snapshot, animatingDifferences: false)
}
func appendNewItems() {
// var snapshot = NSDiffableDataSourceSnapshot()
//
// snapshot.appendSections([.featured, .regular])
var snapshot = dataSource.snapshot()
snapshot.deleteItems(featuredItems.map { $0.id })
self.featuredItems = [
FeaturedItem(id: UUID(), title: "Featured Item 1", subtitle: "Subtitle 1")
// FeaturedItem(id: UUID(), title: "Featured Item 2", subtitle: "Subtitle 2"),
].shuffled()
self.regularItems = [
// RegularItem(id: UUID(), description: "Regular Item 2")
// RegularItem(id: UUID(), description: "Regular Item 3"),
].shuffled()
snapshot.appendItems(featuredItems.map { $0.id })
// snapshot.appendItems(regularItems.map { $0.id }, toSection: .regular)
dataSource.apply(snapshot, animatingDifferences: true)
}
// Update the configureDataSource method to use custom cells
private func configureDataSource() {
dataSource = UITableViewDiffableDataSource(
tableView: tableView
) { tableView, indexPath, itemIdentifier in
if let featuredItem = self.featuredItems.first(where: { $0.id == itemIdentifier }) {
let cell =
tableView.dequeueReusableCell(withIdentifier: "FeaturedCell", for: indexPath)
as! FeaturedCell
cell.configure(with: featuredItem)
return cell
} else if let regularItem = self.regularItems.first(where: { $0.id == itemIdentifier }) {
let cell =
tableView.dequeueReusableCell(withIdentifier: "RegularCell", for: indexPath)
as! RegularCell
cell.configure(with: regularItem)
return cell
}
return UITableViewCell()
}
}
private var featuredItems: [FeaturedItem] = []
private var regularItems: [RegularItem] = []
private func applyInitialSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot()
featuredItems = [
FeaturedItem(id: UUID(), title: "Featured Item 1", subtitle: "Subtitle 1"),
]
// regularItems = [
// RegularItem(id: UUID(), description: "Regular Item 1"),
// RegularItem(id: UUID(), description: "Regular Item 2"),
// RegularItem(id: UUID(), description: "Regular Item 3"),
// ]
snapshot.appendSections([.featured, .regular])
snapshot.appendItems(featuredItems.map { $0.id }, toSection: .featured)
// snapshot.appendItems(regularItems.map { $0.id }, toSection: .regular)
dataSource.apply(snapshot, animatingDifferences: true)
}
}
import SwiftUI
class RegularTableViewExampleViewController: UIViewController {
private var tableView: UITableView!
private var featuredItems: [FeaturedItem] = []
private var regularItems: [RegularItem] = []
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupTableView()
applyInitialData()
}
private func setupTableView() {
tableView = UITableView(frame: view.bounds, style: .insetGrouped)
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableView.register(FeaturedCell.self, forCellReuseIdentifier: "FeaturedCell")
tableView.register(RegularCell.self, forCellReuseIdentifier: "RegularCell")
tableView.dataSource = self
view.addSubview(tableView)
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "Reload",
style: .plain,
target: self,
action: #selector(reloadData)
)
}
@objc private func reloadData() {
appendNewItems()
tableView.reloadData()
}
private func applyInitialData() {
featuredItems = [
FeaturedItem(id: UUID(), title: "Featured Item 1", subtitle: "Subtitle 1")
]
regularItems = [
]
}
private func appendNewItems() {
featuredItems.removeAll()
featuredItems.append(FeaturedItem(id: UUID(), title: "New Featured Item", subtitle: "New Subtitle"))
}
}
extension RegularTableViewExampleViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return featuredItems.count
case 1:
return regularItems.count
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "FeaturedCell", for: indexPath) as! FeaturedCell
cell.configure(with: featuredItems[indexPath.row])
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "RegularCell", for: indexPath) as! RegularCell
cell.configure(with: regularItems[indexPath.row])
return cell
default:
return UITableViewCell()
}
}
}
import AVKit
struct FeaturedCellView: View {
let featuredItem: FeaturedItem
@State private var player = AVPlayer(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")!)
var body: some View {
VStack {
Text(featuredItem.title)
.font(.headline)
Text(featuredItem.subtitle)
.font(.subheadline)
VideoPlayer(player: player)
.frame(height: 200)
}.onAppear {
print("FeaturedCellView onAppear for item: \(featuredItem.title)")
player.play()
}
.onDisappear {
print("FeaturedCellView onDisappear for item: \(featuredItem.title)")
player.pause()
}
}
}
// add preview
#Preview {
FeaturedCellView(featuredItem: FeaturedItem(id: UUID(), title: "Featured Item 1", subtitle: "Subtitle 1"))
}
struct RegularCellView: View {
let regularItem: RegularItem
@State private var player = AVPlayer(url: URL(string: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")!)
var body: some View {
VStack {
Text(regularItem.description)
.font(.body)
VideoPlayer(player: player)
.frame(height: 200)
}.onAppear {
print("RegularCellView onAppear with item: \(regularItem.description)")
player.play()
}
.onDisappear {
print("RegularCellView onDisappear with item: \(regularItem.description)")
player.pause()
}
}
}
class FeaturedCell: UITableViewCell {
private var hostingController: UIHostingController?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
print("FeaturedCell init")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with featuredItem: FeaturedItem) {
let view = FeaturedCellView(featuredItem: featuredItem)
hostingController = UIHostingController(rootView: view)
if let hostingController = hostingController {
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(hostingController.view)
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: contentView.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
])
}
}
override func prepareForReuse() {
super.prepareForReuse()
print("FeaturedCell prepareForReuse")
hostingController?.view.removeFromSuperview()
hostingController = nil
}
deinit {
print("FeaturedCell deallocated")
}
}
class RegularCell: UITableViewCell {
private var hostingController: UIHostingController?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
print("RegularCell init")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with regularItem: RegularItem) {
let view = RegularCellView(regularItem: regularItem)
hostingController = UIHostingController(rootView: view)
if let hostingController = hostingController {
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(hostingController.view)
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: contentView.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
])
}
}
override func prepareForReuse() {
super.prepareForReuse()
print("RegularCell prepareForReuse")
hostingController?.view.removeFromSuperview()
hostingController = nil
}
deinit {
print("RegularCell deallocated")
}
}
https://gist.github.com/SURYAKANTSHARMA ... 00ba5aed17 Мы будем очень признательны за любые идеи или предложения по этим вопросам!
Заранее спасибо !
Подробнее здесь:
https://stackoverflow.com/questions/793 ... r-the-firs
1736484622
Anonymous
Я работаю над приложением для iOS, используя как UITableViewDiffableDataSource, так и SwiftUI, и столкнулся с двумя отдельными, но загадочными проблемами: UITableViewDiffableDataSource не использует повторно ячейки при первом применении после первоначального снимка. После первого применения он работает так, как ожидалось, со второго раза. SwiftUI View внутри UITableViewCell onDisappear Не запускает первые изменения снимка после первоначального снимка. После этого onDispear вызывает, как и ожидалось. С обычным UITableView он работает нормально. Вызывает проблему — из-за этого проигрыватель и ячейки сильно сохраняют память. Пример кода для воспроизведения с использованием diffable (DiffableTableViewExampleViewController) и нормальной работы без diffable (RegularTableViewExampleViewController) [code]import UIKit enum TableSection: Hashable { case featured case regular } struct FeaturedItem: Hashable { let id: UUID let title: String let subtitle: String } struct RegularItem: Hashable { let id: UUID let description: String } class DiffableTableViewExampleViewController: UIViewController { private var tableView: UITableView! private var dataSource: UITableViewDiffableDataSource! override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white setupTableView() configureDataSource() applyInitialSnapshot() } // Update the setupTableView method to use custom cells private func setupTableView() { tableView = UITableView(frame: view.bounds, style: .insetGrouped) tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] tableView.register(FeaturedCell.self, forCellReuseIdentifier: "FeaturedCell") tableView.register(RegularCell.self, forCellReuseIdentifier: "RegularCell") view.addSubview(tableView) navigationItem.rightBarButtonItems = [ UIBarButtonItem( title: "Reload", style: .plain, target: self, action: #selector(reloadSnapshot) ), UIBarButtonItem( title: "Regular View", style: .plain, target: self, action: #selector(navigateToRegularView) ) ] } @objc private func navigateToRegularView() { let regularVC = RegularTableViewExampleViewController() navigationController?.pushViewController(regularVC, animated: true) } @objc private func reloadSnapshot() { // deleteSections() self.appendNewItems() DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // self.applyInitialSnapshot() } //appendNewItems() } func deleteSections() { var snapshot = dataSource.snapshot() snapshot.deleteSections([.regular, .featured]) dataSource.apply(snapshot, animatingDifferences: false) } func appendNewItems() { // var snapshot = NSDiffableDataSourceSnapshot() // // snapshot.appendSections([.featured, .regular]) var snapshot = dataSource.snapshot() snapshot.deleteItems(featuredItems.map { $0.id }) self.featuredItems = [ FeaturedItem(id: UUID(), title: "Featured Item 1", subtitle: "Subtitle 1") // FeaturedItem(id: UUID(), title: "Featured Item 2", subtitle: "Subtitle 2"), ].shuffled() self.regularItems = [ // RegularItem(id: UUID(), description: "Regular Item 2") // RegularItem(id: UUID(), description: "Regular Item 3"), ].shuffled() snapshot.appendItems(featuredItems.map { $0.id }) // snapshot.appendItems(regularItems.map { $0.id }, toSection: .regular) dataSource.apply(snapshot, animatingDifferences: true) } // Update the configureDataSource method to use custom cells private func configureDataSource() { dataSource = UITableViewDiffableDataSource( tableView: tableView ) { tableView, indexPath, itemIdentifier in if let featuredItem = self.featuredItems.first(where: { $0.id == itemIdentifier }) { let cell = tableView.dequeueReusableCell(withIdentifier: "FeaturedCell", for: indexPath) as! FeaturedCell cell.configure(with: featuredItem) return cell } else if let regularItem = self.regularItems.first(where: { $0.id == itemIdentifier }) { let cell = tableView.dequeueReusableCell(withIdentifier: "RegularCell", for: indexPath) as! RegularCell cell.configure(with: regularItem) return cell } return UITableViewCell() } } private var featuredItems: [FeaturedItem] = [] private var regularItems: [RegularItem] = [] private func applyInitialSnapshot() { var snapshot = NSDiffableDataSourceSnapshot() featuredItems = [ FeaturedItem(id: UUID(), title: "Featured Item 1", subtitle: "Subtitle 1"), ] // regularItems = [ // RegularItem(id: UUID(), description: "Regular Item 1"), // RegularItem(id: UUID(), description: "Regular Item 2"), // RegularItem(id: UUID(), description: "Regular Item 3"), // ] snapshot.appendSections([.featured, .regular]) snapshot.appendItems(featuredItems.map { $0.id }, toSection: .featured) // snapshot.appendItems(regularItems.map { $0.id }, toSection: .regular) dataSource.apply(snapshot, animatingDifferences: true) } } import SwiftUI class RegularTableViewExampleViewController: UIViewController { private var tableView: UITableView! private var featuredItems: [FeaturedItem] = [] private var regularItems: [RegularItem] = [] override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white setupTableView() applyInitialData() } private func setupTableView() { tableView = UITableView(frame: view.bounds, style: .insetGrouped) tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] tableView.register(FeaturedCell.self, forCellReuseIdentifier: "FeaturedCell") tableView.register(RegularCell.self, forCellReuseIdentifier: "RegularCell") tableView.dataSource = self view.addSubview(tableView) navigationItem.rightBarButtonItem = UIBarButtonItem( title: "Reload", style: .plain, target: self, action: #selector(reloadData) ) } @objc private func reloadData() { appendNewItems() tableView.reloadData() } private func applyInitialData() { featuredItems = [ FeaturedItem(id: UUID(), title: "Featured Item 1", subtitle: "Subtitle 1") ] regularItems = [ ] } private func appendNewItems() { featuredItems.removeAll() featuredItems.append(FeaturedItem(id: UUID(), title: "New Featured Item", subtitle: "New Subtitle")) } } extension RegularTableViewExampleViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { return 2 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { case 0: return featuredItems.count case 1: return regularItems.count default: return 0 } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.section { case 0: let cell = tableView.dequeueReusableCell(withIdentifier: "FeaturedCell", for: indexPath) as! FeaturedCell cell.configure(with: featuredItems[indexPath.row]) return cell case 1: let cell = tableView.dequeueReusableCell(withIdentifier: "RegularCell", for: indexPath) as! RegularCell cell.configure(with: regularItems[indexPath.row]) return cell default: return UITableViewCell() } } } import AVKit struct FeaturedCellView: View { let featuredItem: FeaturedItem @State private var player = AVPlayer(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")!) var body: some View { VStack { Text(featuredItem.title) .font(.headline) Text(featuredItem.subtitle) .font(.subheadline) VideoPlayer(player: player) .frame(height: 200) }.onAppear { print("FeaturedCellView onAppear for item: \(featuredItem.title)") player.play() } .onDisappear { print("FeaturedCellView onDisappear for item: \(featuredItem.title)") player.pause() } } } // add preview #Preview { FeaturedCellView(featuredItem: FeaturedItem(id: UUID(), title: "Featured Item 1", subtitle: "Subtitle 1")) } struct RegularCellView: View { let regularItem: RegularItem @State private var player = AVPlayer(url: URL(string: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")!) var body: some View { VStack { Text(regularItem.description) .font(.body) VideoPlayer(player: player) .frame(height: 200) }.onAppear { print("RegularCellView onAppear with item: \(regularItem.description)") player.play() } .onDisappear { print("RegularCellView onDisappear with item: \(regularItem.description)") player.pause() } } } class FeaturedCell: UITableViewCell { private var hostingController: UIHostingController? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) print("FeaturedCell init") } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configure(with featuredItem: FeaturedItem) { let view = FeaturedCellView(featuredItem: featuredItem) hostingController = UIHostingController(rootView: view) if let hostingController = hostingController { hostingController.view.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(hostingController.view) NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: contentView.topAnchor), hostingController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), hostingController.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) ]) } } override func prepareForReuse() { super.prepareForReuse() print("FeaturedCell prepareForReuse") hostingController?.view.removeFromSuperview() hostingController = nil } deinit { print("FeaturedCell deallocated") } } class RegularCell: UITableViewCell { private var hostingController: UIHostingController? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .default, reuseIdentifier: reuseIdentifier) print("RegularCell init") } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configure(with regularItem: RegularItem) { let view = RegularCellView(regularItem: regularItem) hostingController = UIHostingController(rootView: view) if let hostingController = hostingController { hostingController.view.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(hostingController.view) NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: contentView.topAnchor), hostingController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), hostingController.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) ]) } } override func prepareForReuse() { super.prepareForReuse() print("RegularCell prepareForReuse") hostingController?.view.removeFromSuperview() hostingController = nil } deinit { print("RegularCell deallocated") } } [/code] https://gist.github.com/SURYAKANTSHARMA/d83fa9e7e0de309e27485100ba5aed17 Мы будем очень признательны за любые идеи или предложения по этим вопросам! Заранее спасибо ! Подробнее здесь: [url]https://stackoverflow.com/questions/79342892/in-swiftui-the-ondisappear-method-of-a-view-is-not-being-triggered-for-the-firs[/url]