Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xcode11 exhibits warnings if you bind in viewDidLoad #331

Open
SlashDevSlashGnoll opened this issue Aug 29, 2019 · 11 comments
Open

Xcode11 exhibits warnings if you bind in viewDidLoad #331

SlashDevSlashGnoll opened this issue Aug 29, 2019 · 11 comments

Comments

@SlashDevSlashGnoll
Copy link

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.

@rikvdbrule
Copy link

rikvdbrule commented Sep 10, 2019

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?

@SlashDevSlashGnoll
Copy link
Author

SlashDevSlashGnoll commented Sep 11, 2019

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.

@JT501
Copy link

JT501 commented Oct 8, 2019

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

@halonsoluis
Copy link

halonsoluis commented Nov 28, 2019

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.

@eralnar
Copy link

eralnar commented Apr 28, 2020

Hey guys, any update on this?

@mashe
Copy link
Contributor

mashe commented May 9, 2020

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

@goa
Copy link

goa commented Jul 16, 2020

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

@wangdenkun
Copy link

wangdenkun commented Jul 22, 2020

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)

@axmav
Copy link

axmav commented Oct 10, 2020

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>>

@sreznic
Copy link

sreznic commented Dec 1, 2020

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

@bdrobert
Copy link

bdrobert commented Dec 19, 2020

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests