featherless aa62eeb107
[NavigationBar] Fix bug where titleView would disappear. (#7310)
Closes https://github.com/material-components/material-components-ios/issues/7207

Context
-------

UINavigationBar will attempt to steal a navigationItem's titleView property for its own use, even if the navigation bar is not visible. Our own MDCNavigationBar also wants to show the titleView instance but we can't stop UINavigationBar from stealing the titleView.

To protect against this behavior, MDCNavigationBar implements a "sandbag swap" of the titleView when it's assigned. UINavigationBar ends up stealing the sandbag away, while our MDCNavigationBar keeps an internal reference to the desired titleView.

Before this fix
---------------

The sandbag view would be swapped the first time titleView is assigned. If the same titleView was assigned to the navigationItem again, however, we would not swap it with a sandbag. The result is that navigationItem.titleView would be pointing at the actual view (not the sandbag) when UINavigationBar comes around to steal the titleView. The result is that the titleView would disappear from MDCNavigationBar.

After this fix
--------------

We now assign the sandbag view on every assignment, regardless of whether the view is the same or not.

I wrote a test to simulate the theft behavior of UINavigationBar. I verified that the test failed before this patch and that it passes after this patch. I also verified the original bug with the internal client's code and example.
2019-05-01 19:37:34 +03:00

143 lines
4.2 KiB
Swift

// Copyright 2019-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import XCTest
import MaterialComponents.MaterialNavigationBar
class NavigationBarTitleViewTests: XCTestCase {
// MARK: Direct assignment
func testTitleViewAssignmentAddsItAsASubview() {
// Given
let navigationBar = MDCNavigationBar()
let titleView = UIView()
// When
navigationBar.titleView = titleView
// Then
XCTAssertEqual(titleView.superview, navigationBar)
}
func testTitleViewAssignmentTwiceStillAddsItAsASubview() {
// Given
let navigationBar = MDCNavigationBar()
let titleView = UIView()
// When
navigationBar.titleView = titleView
navigationBar.titleView = titleView
// Then
XCTAssertEqual(titleView.superview, navigationBar)
}
func testTitleViewAssignmentAndThenRemovalFromViewHierarchyLeavesItWithoutASuperview() {
// Given
let navigationBar = MDCNavigationBar()
let titleView = UIView()
// When
navigationBar.titleView = titleView
titleView.removeFromSuperview()
// Then
XCTAssertNil(titleView.superview)
}
// Designed to keep https://github.com/material-components/material-components-ios/issues/7207
// from regressing.
func testTitleViewAssignmentThenRemovalFromViewHierarchyThenReassignmentAddsItAsASubview() {
// Given
let navigationBar = MDCNavigationBar()
let titleView = UIView()
// When
navigationBar.titleView = titleView
titleView.removeFromSuperview()
navigationBar.titleView = titleView
// Then
XCTAssertEqual(titleView.superview, navigationBar)
}
// MARK: Assignment view UINavigationItem
func testNavigationItemTitleViewAssignmentBeforeObservationAddsTitleViewAsASubview() {
// Given
let navigationBar = MDCNavigationBar()
let titleView = UIView()
let navigationItem = UINavigationItem()
// When
navigationItem.titleView = titleView
navigationBar.observe(navigationItem)
// Then
XCTAssertEqual(titleView.superview, navigationBar)
}
func testNavigationItemTitleViewAssignmentAfterObservationAddsTitleViewAsASubview() {
// Given
let navigationBar = MDCNavigationBar()
let titleView = UIView()
let navigationItem = UINavigationItem()
// When
navigationBar.observe(navigationItem)
navigationItem.titleView = titleView
// Then
XCTAssertEqual(titleView.superview, navigationBar)
}
func testNavigationItemTitleViewAssignmentWithSimulatedTheftKeepsTitleViewAsSubview() {
// Given
let navigationBar = MDCNavigationBar()
let titleView = UIView()
let navigationItem = UINavigationItem()
let simulatedThiefView = UIView()
// When
navigationItem.titleView = titleView
navigationBar.observe(navigationItem)
simulatedThiefView.addSubview(navigationItem.titleView!)
// Then
XCTAssertEqual(navigationItem.titleView?.superview, simulatedThiefView)
XCTAssertEqual(titleView.superview, navigationBar)
}
// Designed to keep https://github.com/material-components/material-components-ios/issues/7207
// from regressing.
func testNavigationItemTitleViewAssignmentWithReassignmentThenTheftKeepsTitleViewAsSubview() {
// Given
let navigationBar = MDCNavigationBar()
let titleView = UIView()
let navigationItem = UINavigationItem()
let simulatedThiefView = UIView()
// When
navigationItem.titleView = titleView
navigationBar.observe(navigationItem)
navigationItem.titleView = titleView
simulatedThiefView.addSubview(navigationItem.titleView!)
// Then
XCTAssertEqual(navigationItem.titleView?.superview, simulatedThiefView)
XCTAssertEqual(titleView.superview, navigationBar)
}
}