RxDataSources icon indicating copy to clipboard operation
RxDataSources copied to clipboard

Xcode11 exhibits warnings if you bind in viewDidLoad

Open SlashDevSlashGnoll opened this issue 6 years ago • 11 comments

Since Xcode11 we're seeing the warnings that were addressed in #75 return. The example project exhibits this warning also if you tap the Customization using table view delegate in the example app. Obviously this happens a lot more in a large application with lots of screens.

It's not clear to me if this is a UIKit bug or something that needs addressing in RxDatasources. It feels like the former as I can't imagine how the toolkit can complain that a view isn't in the hierarchy in viewDidLoad but I wanted to log something to track the issue as this beta season develops.

SlashDevSlashGnoll avatar Aug 29 '19 15:08 SlashDevSlashGnoll

Can confirm. This is a warning that was introduced in Xcode 11 when views are laid out before the viewController is added to the view hierarchy.

The warning in the console is this:

Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window. Table view: <UITableView: 0x7fcef4069e00; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x600002515c20>; layer = <CALayer: 0x600002a342c0>; contentOffset: {0, 0}; contentSize: {414, 0}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <RxCocoa.RxTableViewDataSourceProxy: 0x600000e475a0>>

Adding the symbolic breakpoint breaks on DelegateProxyType line 329.

The workaround is to bind your dataSource after the view was added to the view hierarchy. In a UIViewController, that moment is when viewDidAppear is called. However, this is quite inconvenient since this method may be called more than once in the lifetime of a view (when navigating to and from the view for instance). So that means you need to manage the Disposable manually or place guards around the subscription. Either way, not ideal.

Since the DelegateProxyType lives in RxCocoa, should we also open an issue there?

rikvdbrule avatar Sep 10 '19 15:09 rikvdbrule

However, this is quite inconvenient since this method may be called more than once in the lifetime of a view (

This isn't even the worst aspect. You can visually see the UI change if your labels have placeholder values from a storyboard, etc. It's just way too late in the lifecycle to do this.

SlashDevSlashGnoll avatar Sep 11 '19 19:09 SlashDevSlashGnoll

Bump. I got the same issue. Any update ? 😄

JT501 avatar Oct 08 '19 13:10 JT501

In case you don't want to manage the Disposable manually, as you would lose, for example, the position in your tableView if you are binding to it. You could use a way to execute this only once. For now, and with some time pressure, I have come to use:

private lazy var rxReady: Bool = setupRx()

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        if rxReady {
            //Needed for circunvent issue:
            //https://github.com/ReactiveX/RxSwift/issues/2081
            //https://github.com/RxSwiftCommunity/RxDataSources/issues/331

            //Once it's solved, it's safe to move the call to setupRX back into viewDidLoad()
        }
    }

Not proud of it ...but it's a way to circumvent the issue with minimal changes for now until it's fixed in the library.

Another way, cleaner, but not exactly clear as to how robust can it be, is to place inside the viewDidLoad() a call to setupRX() using an async block:

DispatchQueue.main.async {
        self.setupRx()
 }

The other is to ignore the warning... but ...that doesn't seem right.

halonsoluis avatar Nov 28 '19 18:11 halonsoluis

Hey guys, any update on this?

eralnar avatar Apr 28 '20 16:04 eralnar

This pull request fixes this issue: https://github.com/ReactiveX/RxSwift/pull/2076. It is already in develop branch, so should appear on next RxSwift release.

mashe avatar May 09 '20 09:05 mashe

Any idea when the next RxSwift release is coming out? :)

goa avatar Jul 16 '20 17:07 goa

in viewDidLoad or loadView func where you bind dataSource,delay a little while then do the bind task. the waring goes away,but I don`t think this a good way.

        Observable<Int>.timer(RxTimeInterval.milliseconds(10), scheduler: MainScheduler.instance).subscribe(onNext:{ e in
            self.itemsSub.bind(to: self.addressTabelView.rx.items(dataSource: dataSource)).disposed(by: self.disposeBag)
            self.itemsSub.onNext(self.dataList)
        }).disposed(by: disposeBag)

wangdenkun avatar Jul 22 '20 09:07 wangdenkun

same issue for me. I am using RxFeedback: i run Driver.system() in viewDidLoad(), then bind tableView using:

func bind() -> Feedback {
        return bind(self) { me, state in
            let subscriptions: [Disposable] = [
                state.data().drive(me.tableView.rx.items(dataSource: me.dataSource))
            ]
            let events: [Observable<Event>] = []
            return Bindings(subscriptions: subscriptions, events: events)
        }
    }

i tried here to replace my data state.data() driver with Driver<[DataSection]>.empty() not to send any data on tableView.rx.items, but still get a warning:

[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy ...... to a window. Table view: <UITableView: 0x7b7800006c00; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7b0c000b6f70>; layer = <CALayer: 0x7b0800078f60>; contentOffset: {0, 0}; contentSize: {414, 0}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <RxCocoa.RxTableViewDataSourceProxy: 0x7b180000bee0>>

axmav avatar Oct 10 '20 16:10 axmav

Has anyone found the decent fix since? None of the fixes above satisfied my needs.

serhrez avatar Dec 01 '20 22:12 serhrez

I have been encountering this issue and believe I may have a fix. By setting a symbolic breakpoint for UITableViewAlertForLayoutOutsideViewHierarchy I determined the following call to layoutIfNeeded in DelegateProxyType was being called before the tableView is added to window:

extension ObservableType {
            func subscribeProxyDataSource<DelegateProxy: DelegateProxyType>(ofObject object: DelegateProxy.ParentObject, dataSource: DelegateProxy.Delegate, retainDataSource: Bool, binding: @escaping (DelegateProxy, Event<Element>) -> Void)
                -> Disposable
                where DelegateProxy.ParentObject: UIView
                , DelegateProxy.Delegate: AnyObject {
                let proxy = DelegateProxy.proxy(for: object)
                let unregisterDelegate = DelegateProxy.installForwardDelegate(dataSource, retainDelegate: retainDataSource, onProxyForObject: object)
                // this is needed to flush any delayed old state (https://github.com/RxSwiftCommunity/RxDataSources/pull/75)

                /// Potential fix for https://github.com/RxSwiftCommunity/RxDataSources/issues/331
                if let _ = object as? UITableView, object.window != nil {
                    object.layoutIfNeeded()
                }

The check for window resolves the error but I am uncertain if this modification has any other ramifications. Also, this is checking that the object is UITableView, should we consider this for all UIViews?

bdrobert avatar Dec 19 '20 04:12 bdrobert