From 67859885be82c1409d30a2f4964efcd3071dac32 Mon Sep 17 00:00:00 2001 From: Gabriel Lanata Date: Thu, 22 Jul 2021 01:12:55 -0700 Subject: [PATCH 1/2] Add SwiftUI support --- .../EventGenerator+Hand.swift | 8 +- .../EventGenerator+Stylus.swift | 2 +- .../EventGenerator/EventGenerator.swift | 52 +++++-- Sources/Hammer/Utilties/HammerError.swift | 3 + .../Hammer/Utilties/UIKit+Extensions.swift | 30 ---- Sources/Hammer/Utilties/ViewWrapping.swift | 53 +++++++ Sources/Hammer/Utilties/Waiting.swift | 2 +- Tests/HammerTests/HandTests_SwiftUI.swift | 146 ++++++++++++++++++ Tests/HammerTests/KeyboardTests_SwiftUI.swift | 63 ++++++++ 9 files changed, 311 insertions(+), 48 deletions(-) create mode 100644 Sources/Hammer/Utilties/ViewWrapping.swift create mode 100644 Tests/HammerTests/HandTests_SwiftUI.swift create mode 100644 Tests/HammerTests/KeyboardTests_SwiftUI.swift diff --git a/Sources/Hammer/EventGenerator/EventGenerator+Hand/EventGenerator+Hand.swift b/Sources/Hammer/EventGenerator/EventGenerator+Hand/EventGenerator+Hand.swift index ec32a20..bd5072d 100644 --- a/Sources/Hammer/EventGenerator/EventGenerator+Hand/EventGenerator+Hand.swift +++ b/Sources/Hammer/EventGenerator/EventGenerator+Hand/EventGenerator+Hand.swift @@ -37,7 +37,7 @@ extension EventGenerator { /// - parameter index: The finger index to touch down. /// - parameter location: The location where to touch down. Nil to use the center. public func fingerDown(_ index: FingerIndex? = .automatic, at location: HammerLocatable? = nil) throws { - try self.fingerDown([index], at: [location ?? self.mainView]) + try self.fingerDown([index], at: [location ?? self.rootView()]) } /// Sends a finger up event. @@ -217,7 +217,7 @@ extension EventGenerator { angle radians: CGFloat = 0) throws { let indices = try self.fillNextFingerIndices(indices, withExpected: 2) - let location = try (location ?? self.mainView).windowHitPoint(for: self) + let location = try (location ?? self.rootView()).windowHitPoint(for: self) try self.fingerDown(indices, at: location.twoWayOffset(distance, angle: radians)) } @@ -300,7 +300,7 @@ extension EventGenerator { angle radians: CGFloat = 0, duration: TimeInterval) throws { let indices = try self.fillNextFingerIndices(indices, withExpected: 2) - let location = try (location ?? self.mainView).windowHitPoint(for: self) + let location = try (location ?? self.rootView()).windowHitPoint(for: self) let startLocations = location.twoWayOffset(startDistance, angle: radians) let endLocations = location.twoWayOffset(endDistance, angle: radians) try self.fingerDown(indices, at: startLocations) @@ -413,7 +413,7 @@ extension EventGenerator { duration: TimeInterval) throws { let indices = try self.fillNextFingerIndices(indices, withExpected: 2) - let location = try (location ?? self.mainView).windowHitPoint(for: self) + let location = try (location ?? self.rootView()).windowHitPoint(for: self) try self.fingerDown(indices, at: location.twoWayOffset(distance, angle: startRadians)) try self.fingerPivot(indices, aroundAnchor: location, byAngle: endRadians - startRadians, duration: duration) diff --git a/Sources/Hammer/EventGenerator/EventGenerator+Stylus/EventGenerator+Stylus.swift b/Sources/Hammer/EventGenerator/EventGenerator+Stylus/EventGenerator+Stylus.swift index 96ba77d..0f4ccca 100644 --- a/Sources/Hammer/EventGenerator/EventGenerator+Stylus/EventGenerator+Stylus.swift +++ b/Sources/Hammer/EventGenerator/EventGenerator+Stylus/EventGenerator+Stylus.swift @@ -16,7 +16,7 @@ extension EventGenerator { public func stylusDown(at location: HammerLocatable? = nil, azimuth: CGFloat = 0, altitude: CGFloat = 0, pressure: CGFloat = 0) throws { - let location = try (location ?? self.mainView).windowHitPoint(for: self) + let location = try (location ?? self.rootView()).windowHitPoint(for: self) try self.checkPointsAreHittable([location]) try self.sendEvent(stylus: StylusInfo(location: location, phase: .began, pressure: pressure, twist: 0, diff --git a/Sources/Hammer/EventGenerator/EventGenerator.swift b/Sources/Hammer/EventGenerator/EventGenerator.swift index e5c097a..7e8aa39 100644 --- a/Sources/Hammer/EventGenerator/EventGenerator.swift +++ b/Sources/Hammer/EventGenerator/EventGenerator.swift @@ -1,6 +1,9 @@ import CoreGraphics import Foundation import UIKit +#if canImport(SwiftUI) +import SwiftUI +#endif private enum Storage { static var latestEventId: UInt32 = 0 @@ -13,13 +16,9 @@ public final class EventGenerator { /// The window for the events public let window: UIWindow - /// The view that was used to create the event generator - private(set) var mainView: UIView - var activeTouches = TouchStorage() var debugWindow = DebugVisualizerWindow() var eventCallbacks = [UInt32: CompletionHandler]() - private var isUsingCustomWindow: Bool = false /// The default sender id for all events. /// @@ -39,7 +38,6 @@ public final class EventGenerator { self.window = window self.window.layoutIfNeeded() self.debugWindow.frame = self.window.frame - self.mainView = window UIApplication.swizzle() UIApplication.registerForHIDEvents(ObjectIdentifier(self)) { [weak self] event in @@ -63,12 +61,10 @@ public final class EventGenerator { window.backgroundColor = .white } - window.makeKeyAndVisible() + window.isHidden = false window.layoutIfNeeded() try self.init(window: window) - self.isUsingCustomWindow = true - self.mainView = viewController.view } /// Initialize an event generator for a specified UIView. @@ -78,12 +74,21 @@ public final class EventGenerator { /// - parameter view: The view to receive events. public convenience init(view: UIView) throws { try self.init(viewController: UIViewController(wrapping: view)) - self.mainView = view + } + + /// Initialize an event generator for a specified UIView. + /// + /// Event Generator will temporarily create a wrapper UIWindow to send touches. + /// + /// - parameter view: The view to receive events. + @available(iOS 13.0, *) + public convenience init(view: Content) throws { + try self.init(viewController: UIHostingController(wrapping: view)) } deinit { UIApplication.unregisterForHIDEvents(ObjectIdentifier(self)) - if self.isUsingCustomWindow { + if self.window.isWrapper { self.window.isHidden = true self.window.rootViewController = nil self.debugWindow.isHidden = true @@ -98,9 +103,11 @@ public final class EventGenerator { /// Waits until the window is ready to receive user interaction events. /// /// - parameter timeout: The maximum time to wait for the window to be ready. - public func waitUntilWindowIsReady(timeout: TimeInterval = 2) throws { + public func waitUntilWindowIsReady(timeout: TimeInterval = 5) throws { do { - try self.waitUntil(self.isWindowReady, timeout: timeout) + var initialMarkerEventReceived = false + try self.sendMarkerEvent { initialMarkerEventReceived = true } + try self.waitUntil(self.isWindowReady && initialMarkerEventReceived, timeout: timeout) } catch { throw HammerError.windowIsNotReadyForInteraction } @@ -134,6 +141,27 @@ public final class EventGenerator { return true } + /// The root view of the event generator + public func rootView() throws -> UIView { + if self.window.isWrapper { + guard let view = self.window.rootViewController?.view else { + throw HammerError.unableToFindMainView + } + + if view.isWrapper { + guard let wrappedView = view.subviews.first else { + throw HammerError.unableToFindMainView + } + + return wrappedView + } + + return view + } + + return self.window + } + /// Gets the next event ID to use. Event IDs are global and sequential. /// /// - returns: The next event ID. diff --git a/Sources/Hammer/Utilties/HammerError.swift b/Sources/Hammer/Utilties/HammerError.swift index 86fda0c..886156a 100644 --- a/Sources/Hammer/Utilties/HammerError.swift +++ b/Sources/Hammer/Utilties/HammerError.swift @@ -26,6 +26,7 @@ public enum HammerError: Error { case viewIsNotHittable(UIView) case pointIsNotHittable(CGPoint) + case unableToFindMainView case unableToFindView(identifier: String) case invalidViewType(identifier: String, type: String, expected: String) case waitConditionTimeout(TimeInterval) @@ -70,6 +71,8 @@ extension HammerError: CustomStringConvertible { return "View is not in hittable: \(view.shortDescription)" case .pointIsNotHittable(let point): return "Point is not in hittable: \(point)" + case .unableToFindMainView: + return "Unable to find main view" case .unableToFindView(let identifier): return "Unable to find view: \"\(identifier)\"" case .invalidViewType(let identifier, let type, let expected): diff --git a/Sources/Hammer/Utilties/UIKit+Extensions.swift b/Sources/Hammer/Utilties/UIKit+Extensions.swift index 736db24..d28d4e6 100644 --- a/Sources/Hammer/Utilties/UIKit+Extensions.swift +++ b/Sources/Hammer/Utilties/UIKit+Extensions.swift @@ -42,33 +42,3 @@ extension UIDevice { return self.userInterfaceIdiom == .pad } } - -extension UIWindow { - convenience init(wrapping viewController: UIViewController) { - self.init(frame: UIScreen.main.bounds) - self.rootViewController = viewController - } -} - -extension UIViewController { - convenience init(wrapping view: UIView) { - self.init(nibName: nil, bundle: nil) - view.translatesAutoresizingMaskIntoConstraints = false - self.view.addSubview(view) - NSLayoutConstraint.activate([ - view.topAnchor.constraint(equalTo: self.view.topAnchor).priority(.defaultHigh), - view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).priority(.defaultHigh), - view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).priority(.defaultHigh), - view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).priority(.defaultHigh), - view.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), - view.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), - ]) - } -} - -extension NSLayoutConstraint { - fileprivate func priority(_ priority: UILayoutPriority) -> NSLayoutConstraint { - self.priority = priority - return self - } -} diff --git a/Sources/Hammer/Utilties/ViewWrapping.swift b/Sources/Hammer/Utilties/ViewWrapping.swift new file mode 100644 index 0000000..1287df0 --- /dev/null +++ b/Sources/Hammer/Utilties/ViewWrapping.swift @@ -0,0 +1,53 @@ +import UIKit +#if canImport(SwiftUI) +import SwiftUI +#endif + +private let kHammerWrapperIdentifier = "hammer_wrapper" + +extension UIWindow { + convenience init(wrapping viewController: UIViewController) { + self.init(frame: UIScreen.main.bounds) + self.accessibilityIdentifier = kHammerWrapperIdentifier + self.rootViewController = viewController + } +} + +extension UIViewController { + convenience init(wrapping view: UIView) { + self.init(nibName: nil, bundle: nil) + self.view.accessibilityIdentifier = kHammerWrapperIdentifier + view.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(view) + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: self.view.topAnchor).priority(.defaultHigh), + view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).priority(.defaultHigh), + view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).priority(.defaultHigh), + view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).priority(.defaultHigh), + view.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), + view.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + ]) + } +} + +@available(iOS 13.0, *) +extension UIHostingController { + convenience init(wrapping view: Content) { + self.init(rootView: view) + self.view.accessibilityIdentifier = kHammerWrapperIdentifier + } +} + +extension UIView { + /// If the view is a wrapper created by Hammer + var isWrapper: Bool { + return self.accessibilityIdentifier == kHammerWrapperIdentifier + } +} + +extension NSLayoutConstraint { + fileprivate func priority(_ priority: UILayoutPriority) -> NSLayoutConstraint { + self.priority = priority + return self + } +} diff --git a/Sources/Hammer/Utilties/Waiting.swift b/Sources/Hammer/Utilties/Waiting.swift index c1bd68b..895df11 100644 --- a/Sources/Hammer/Utilties/Waiting.swift +++ b/Sources/Hammer/Utilties/Waiting.swift @@ -180,7 +180,7 @@ extension EventGenerator { /// /// - throws: An error if the point is not hittable within the specified time. public func waitUntilHittable(timeout: TimeInterval, checkInterval: TimeInterval = 0.1) throws { - try self.waitUntil(self.viewIsHittable(self.mainView), + try self.waitUntil(self.viewIsHittable(self.rootView()), timeout: timeout, checkInterval: checkInterval) } } diff --git a/Tests/HammerTests/HandTests_SwiftUI.swift b/Tests/HammerTests/HandTests_SwiftUI.swift new file mode 100644 index 0000000..0d05be2 --- /dev/null +++ b/Tests/HammerTests/HandTests_SwiftUI.swift @@ -0,0 +1,146 @@ +import Hammer +import XCTest +#if canImport(SwiftUI) +import SwiftUI +#endif + +final class HandTests_SwiftUI: XCTestCase { + func testButtonTap() throws { + guard #available(iOS 13.0, *) else { + throw XCTSkip("SwiftUI tests require iOS 13 or later") + } + + let expectation = XCTestExpectation(description: "Button Tapped") + + let view = Button("Button", action: expectation.fulfill) + .background(Color.green) + + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.waitUntilHittable(timeout: 1) + try eventGenerator.fingerTap() + + XCTAssertEqual(XCTWaiter.wait(for: [expectation], timeout: 1), .completed) + } + + func testButtonTapOnHidden() throws { + guard #available(iOS 13.0, *) else { + throw XCTSkip("SwiftUI tests require iOS 13 or later") + } + + let view = Button("Button", action: {}) + .background(Color.green) + .hidden() + + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.wait(0.5) + + do { + try eventGenerator.fingerTap() + XCTFail("Button should not be tappable") + } catch HammerError.unableToFindMainView { + // Success + } catch { + throw error + } + } + + func testButtonTapOnMinimumAlpha() throws { + guard #available(iOS 13.0, *) else { + throw XCTSkip("SwiftUI tests require iOS 13 or later") + } + + let view = Button("Button", action: {}) + .background(Color.green) + .opacity(0.001) + + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.wait(0.5) + + do { + try eventGenerator.fingerTap() + XCTFail("Button should not be tappable") + } catch HammerError.viewIsNotVisible { + // Success + } catch { + throw error + } + } + + func testViewTapGesture() throws { + guard #available(iOS 13.0, *) else { + throw XCTSkip("SwiftUI tests require iOS 13 or later") + } + + let expectation = XCTestExpectation(description: "Gesture Tapped") + + let view = Text("Hello World") + .background(Color.green) + .onTapGesture(perform: expectation.fulfill) + + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.waitUntilHittable(timeout: 1) + try eventGenerator.fingerTap() + + XCTAssertEqual(XCTWaiter.wait(for: [expectation], timeout: 1), .completed) + } + + func testViewLongPressGesture() throws { + guard #available(iOS 13.0, *) else { + throw XCTSkip("SwiftUI tests require iOS 13 or later") + } + + let expectation = XCTestExpectation(description: "Gesture Long Pressed Tapped") + + let view = Text("Hello World") + .background(Color.green) + .onLongPressGesture(perform: expectation.fulfill) + + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.waitUntilHittable(timeout: 1) + try eventGenerator.fingerLongPress() + + XCTAssertEqual(XCTWaiter.wait(for: [expectation], timeout: 3), .completed) + } + + func testSwitchToggle() throws { + guard #available(iOS 13.0, *) else { + throw XCTSkip("SwiftUI tests require iOS 13 or later") + } + + var isOn = false + let isOnBinding = Binding(get: { isOn }, set: { isOn = $0 }) + let view = Toggle(isOn: isOnBinding) { EmptyView() } + + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.waitUntilHittable(timeout: 1) + + XCTAssertFalse(isOn) + try eventGenerator.fingerTap() + try eventGenerator.wait(1) + XCTAssertTrue(isOn) + } + + func testSwitchToggleOnOff() throws { + guard #available(iOS 13.0, *) else { + throw XCTSkip("SwiftUI tests require iOS 13 or later") + } + + var isOn = false + let isOnBinding = Binding(get: { isOn }, set: { isOn = $0 }) + let view = Toggle(isOn: isOnBinding) { EmptyView() } + + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.waitUntilHittable(timeout: 1) + let rootView = try eventGenerator.rootView() + + XCTAssertFalse(isOn) + try eventGenerator.fingerDown(at: rootView.frame.center.offset(x: -20, y: 0)) + try eventGenerator.fingerMove(to: rootView.frame.center.offset(x: 40, y: 0), duration: 0.5) + try eventGenerator.fingerUp() + XCTAssertTrue(isOn) + try eventGenerator.fingerDown(at: rootView.frame.center.offset(x: 20, y: 0)) + try eventGenerator.fingerMove(to: rootView.frame.center.offset(x: -40, y: 0), duration: 0.5) + try eventGenerator.fingerUp() + XCTAssertFalse(isOn) + } +} diff --git a/Tests/HammerTests/KeyboardTests_SwiftUI.swift b/Tests/HammerTests/KeyboardTests_SwiftUI.swift new file mode 100644 index 0000000..14357c9 --- /dev/null +++ b/Tests/HammerTests/KeyboardTests_SwiftUI.swift @@ -0,0 +1,63 @@ +import Hammer +import XCTest +#if canImport(SwiftUI) +import SwiftUI +#endif + +final class KeyboardTests_SwiftUI: XCTestCase { + func testTypeOnTextField() throws { + guard #available(iOS 13.0, *) else { + throw XCTSkip("SwiftUI tests require iOS 13 or later") + } + + var text = "" + let textBinding = Binding(get: { text }, set: { text = $0 }) + let view = TextField("TextField", text: textBinding) + .autocapitalization(.none) + .disableAutocorrection(true) + .frame(width: 300) + + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.waitUntilHittable(timeout: 1) + + try eventGenerator.fingerTap() + try eventGenerator.wait(0.5) + + XCTAssertEqual(text, "") + try eventGenerator.keyType("abc") + XCTAssertEqual(text, "abc") + } + + func testKeyOnTextField() throws { + let view = UITextField() + view.disablePredictiveBar() + view.autocapitalizationType = .none + view.widthAnchor.constraint(equalToConstant: 300).isActive = true + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.waitUntilHittable(timeout: 1) + + try eventGenerator.fingerTap() + XCTAssertTrue(view.isFirstResponder) + + XCTAssertEqual(view.text, "") + try eventGenerator.keyDown("a") + try eventGenerator.keyUp("a") + XCTAssertEqual(view.text, "a") + } +// +// func testEnsureKeyWindowError() throws { +// let view = UITextField() +// view.disablePredictiveBar() +// view.autocapitalizationType = .none +// view.widthAnchor.constraint(equalToConstant: 300).isActive = true +// let eventGenerator = try EventGenerator(view: view) +// try eventGenerator.waitUntilHittable(timeout: 1) +// +// do { +// try eventGenerator.keyType("a") +// XCTFail("Expected error") +// } catch HammerError.windowIsNotKey { +// // Success +// } +// } +} From dcb6a898ddd97eaed69fe52ec65ccd0ec06b6e8d Mon Sep 17 00:00:00 2001 From: Gabriel Lanata Date: Thu, 22 Jul 2021 01:54:11 -0700 Subject: [PATCH 2/2] Add accessibility identifier tests --- Sources/Hammer/Utilties/Waiting.swift | 11 ++++++ .../AccessibilityIdentifierTests.swift | 38 +++++++++++++++++++ Tests/HammerTests/HandTests.swift | 8 ---- .../Utilities/UIKit+Extensions.swift | 8 ++++ 4 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 Tests/HammerTests/AccessibilityIdentifierTests.swift diff --git a/Sources/Hammer/Utilties/Waiting.swift b/Sources/Hammer/Utilties/Waiting.swift index 895df11..e595712 100644 --- a/Sources/Hammer/Utilties/Waiting.swift +++ b/Sources/Hammer/Utilties/Waiting.swift @@ -131,6 +131,17 @@ extension EventGenerator { timeout: timeout, checkInterval: checkInterval) } + /// Waits for the root view to be visible on screen within the specified time. + /// + /// - parameter timeout: The maximum time to wait for the point to be hittable. + /// - parameter checkInterval: How often should the view be checked. + /// + /// - throws: An error if the point is not hittable within the specified time. + public func waitUntilVisible(timeout: TimeInterval, checkInterval: TimeInterval = 0.1) throws { + try self.waitUntil(self.viewIsVisible(self.rootView()), + timeout: timeout, checkInterval: checkInterval) + } + /// Waits for a view with the specified identifier to be hittable within the specified time. /// /// - parameter accessibilityIdentifier: The identifier of the view to wait for. diff --git a/Tests/HammerTests/AccessibilityIdentifierTests.swift b/Tests/HammerTests/AccessibilityIdentifierTests.swift new file mode 100644 index 0000000..daf918a --- /dev/null +++ b/Tests/HammerTests/AccessibilityIdentifierTests.swift @@ -0,0 +1,38 @@ +import Hammer +import UIKit +import XCTest +#if canImport(SwiftUI) +import SwiftUI +#endif + +final class AccessibilityIdentifierTests: XCTestCase { + func testButtonSearch() throws { + let button = UIButton().size(width: 100, height: 100) + button.accessibilityIdentifier = "my_button" + button.backgroundColor = .green + + let wrapperView = UIStackView(arrangedSubviews: [button]) + + let eventGenerator = try EventGenerator(view: wrapperView) + try eventGenerator.waitUntilVisible(timeout: 5) + let match = try eventGenerator.viewWithIdentifier("my_button") + XCTAssertEqual(button, match) + } + + func testButtonSearch_SwiftUI() throws { + guard #available(iOS 13.0, *) else { + throw XCTSkip("SwiftUI tests require iOS 13 or later") + } + + let view = HStack { + Button("Button", action: {}) + .accessibility(identifier: "my_button") + .background(Color.green) + } + + let eventGenerator = try EventGenerator(view: view) + try eventGenerator.wait(5) + let match = try eventGenerator.viewWithIdentifier("my_button") + print(match) + } +} diff --git a/Tests/HammerTests/HandTests.swift b/Tests/HammerTests/HandTests.swift index f65a562..7589841 100644 --- a/Tests/HammerTests/HandTests.swift +++ b/Tests/HammerTests/HandTests.swift @@ -259,11 +259,3 @@ final class HandTests: XCTestCase { XCTAssertEqual(view.zoomScale, 1, accuracy: 0.1) } } - -extension UIView { - fileprivate func size(width: CGFloat, height: CGFloat) -> Self { - self.widthAnchor.constraint(equalToConstant: width).isActive = true - self.heightAnchor.constraint(equalToConstant: height).isActive = true - return self - } -} diff --git a/Tests/HammerTests/Utilities/UIKit+Extensions.swift b/Tests/HammerTests/Utilities/UIKit+Extensions.swift index 3d02522..0d3e34c 100644 --- a/Tests/HammerTests/Utilities/UIKit+Extensions.swift +++ b/Tests/HammerTests/Utilities/UIKit+Extensions.swift @@ -20,6 +20,14 @@ extension CGPoint { } } +extension UIView { + func size(width: CGFloat, height: CGFloat) -> Self { + self.widthAnchor.constraint(equalToConstant: width).isActive = true + self.heightAnchor.constraint(equalToConstant: height).isActive = true + return self + } +} + extension UITextField { /// Disables the predictive bar which causes autolayout issues func disablePredictiveBar() {