Closed OdedeRachilo closed 1 year ago
First off, this is not a bug in SwiftUI Introspect.
If you do print(collectionView.delegate)
before you set the delegate, you'll see that in fact the collection view already has a delegate assigned to it:
Optional(<_TtGC7SwiftUI31UICollectionViewListCoordinatorGVS_28CollectionViewListDataSourceOs5Never_GOS_19SelectionManagerBoxS2___: 0x104711570>)
This is SwiftUI's internally managed UICollectionViewDelegate
subclass. It's impossible to tell what its implementation looks like or does because SwiftUI is closed source, but more likely than not it takes care of cell presentation and selection, among other things.
When you override that entire delegate with yours, you're discarding all of that built-in SwiftUI implementation, which understandably corrupts the collection view's expected functionality.
There are two workarounds for this, neither of which is pretty:
import SwiftUI
import SwiftUIIntrospect
@main
struct App: SwiftUI.App {
var body: some Scene {
WindowGroup {
List_Introspect_View()
}
}
}
final class TableViewDelegate: NSObject, UITableViewDelegate {}
final class CollectionViewDelegate: NSObject, UICollectionViewDelegate {
private weak var baseDelegate: UICollectionViewDelegate?
init(baseDelegate: UICollectionViewDelegate?) {
self.baseDelegate = baseDelegate
}
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
// extra implementation, if needed
baseDelegate?.collectionView?(collectionView, shouldHighlightItemAt: indexPath) ?? false
// extra implementation, if needed
}
func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
// extra implementation, if needed
baseDelegate?.collectionView?(collectionView, didHighlightItemAt: indexPath)
// extra implementation, if needed
}
func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
// extra implementation, if needed
baseDelegate?.collectionView?(collectionView, didUnhighlightItemAt: indexPath)
// extra implementation, if needed
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// extra implementation, if needed
baseDelegate?.collectionView?(collectionView, willDisplay: cell, forItemAt: indexPath) // this one fixes the issue
// extra implementation, if needed
}
// and so on for every single function...
}
extension UICollectionView {
var strongDelegate: UICollectionViewDelegate? {
get {
let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self)
return objc_getAssociatedObject(self, key) as? UICollectionViewDelegate
}
set {
let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self)
objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
delegate = newValue
}
}
}
struct List_Introspect_View: View {
let url = "https://images.pexels.com/photos/4066041/pexels-photo-4066041.jpeg?auto=compress&cs=tinysrgb&w=1600"
let tableViewDelegate = TableViewDelegate()
var body: some View {
List {
ForEach(0..<50) { idx in
HStack {
AsyncImage(url: URL(string: url)) { image in
image
.resizable()
.scaledToFill()
} placeholder: {
Color.brown
}
.frame(width: 50, height: 50)
.clipShape(Circle())
Spacer()
}
}
.listRowInsets(.init(top: 10, leading: 15, bottom: 10, trailing: 15))
}
.listStyle(.plain)
// .padding(.top, UIApplication.topInset)
.ignoresSafeArea()
.introspect(.list, on: .iOS(.v13, .v14, .v15)) { tableView in
tableView.delegate = tableViewDelegate // working
}
.introspect(.list, on: .iOS(.v16, .v17)) { collectionView in
guard collectionView.strongDelegate == nil else { return }
collectionView.strongDelegate = CollectionViewDelegate(baseDelegate: collectionView.delegate)
}
}
}
collectionView.delegate
to introduce whatever logic you want to whichever function you want, without disturbing the original implementation and without needing to subclass. This is the cleanest approach, and the most maintainable long term, but it's also the most obscure and steepest to learn properly.You've definitely ventured into extremely advanced and nuanced territory here. One of my libraries employs both of these techniques to integrate seamlessly with SwiftUI's internals. I suggest you take a look to it to learn more.
Thanks a pile @davdroman
I stand guided.
And you also pointed me to something I was looking for in the library that you have linked, will certainly be making use of that as well. Thanks
Description
Checklist
Expected behavior
Remote images fetched via AsyncImage should show when the introspected CollectionView is assigned a UICollectionViewDelegate. i.e
collectionView.delegate = collectionViewDelegate
Actual behavior
Remote images fetched via AsyncImage not showing when the introspected CollectionView is assigned a UICollectionViewDelegate. Commenting out the delegate assignment i.e
collectionView.delegate = collectionViewDelegate
// showing// collectionView.delegate = collectionViewDelegate
// not showingSteps to reproduce
Code above
Version information
1.0.0
Destination operating system
iOS 16
Xcode version information
14.3.1 (14E300c)
Swift Compiler version information