Simple problem. I got button which perform segue to next view controller.
I want to write UI XCTest to tell me did it open view controller i wanted.
The UI Testing framework doesn't have access to your applications code which makes class assertions on instances impossible. You are not able to directly tell the class of the controller which is on screen.
However, if you think about your test a little differently you can make a very similar assertion. Write your tests as if you are the user. Your user doesn't care if he/she is looking at a ItemDetailViewController or a ItemListTableViewController so neither should your tests.
The user cares what's on the screen. What's the title? Or, what are the names of these buttons? Following that logic you are rewrite your test to assert based on those items, not the name of the coded class.
For example, if you are presenting your controller in a navigation stack you can assert the title.
let app = XCUIApplication()
app.buttons["View Item"].tap()
XCTAssert(app.navigationBars["Some Item"].exists)
Or, if the screen is presented modally but you know some static text or buttons, use those.
let app = XCUIApplication()
app.buttons["View Item"].tap()
XCTAssert(app.staticTexts["Item Detail"].exists)
XCTAssert(app.buttons["Remove Item"].exists)
Comment of Matt Green gave me a good idea. We can define an unused label/button, ideally inside a base view controller and assign it an accessibility label to perform a query to find out which view controller is presented.
public class BaseViewController: UIViewController {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
public override func viewDidLoad() {
super.viewDidLoad()
if let identifier = self.theClassName.split(separator: ".").last {
button.accessibilityIdentifier = String(identifier)
view.addSubview(button)
}
}
}
public class DatePickerViewController: BaseViewController {
...
}
func testExample() {
let app = XCUIApplication()
app.launch()
app.navigationBars.buttons["DateSelector"].tap()
XCTAssertTrue(app.buttons["DatePickerViewController"].exists)
}
Note that inorder to make this approach work you have to add the view you use to identify view controller, in this case a button, should be added as a sub view and has to have a non zero frame.
Related
I've created a ViewController containing a user button, which is going to be present in several View Controllers in my application.
I'm adding this ViewController dynamically to the needed ViewControllers. The user button is shown, but it's not clickable. What am I doing wrong?
I've tried setting constraints to the view containing the button, setting the container view's frame, disabling user interaction in the container view (not in the button) and nothing seems to work
import UIKit
class ModulePageViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.addSharedButtonsSubView()
}
func addSharedButtonsSubView() {
let sharedButtons = storyboard?.instantiateViewController(withIdentifier: sharedButtonsViewControllerName)
view.addSubview((sharedButtons?.view)!)
sharedButtons?.view.frame = CGRect(x: view.frame.minX, y: view.frame.minY, width: view.frame.width, height: view.frame.height)
addChild(sharedButtons!)
sharedButtons?.didMove(toParent: self)
}
}
You can create a custom view (not ViewController) containing the button and just use it where you need in you app.
#LeCalore ...
I would recommend if you want to use a button or any more stuff on multiple View Controllers then you should just make a new ViewController with that button and whatever else you want on it then use it where ever you want.
ViewController -> Present As Pop Over (Presentation : Over Current Context)
I think that's a better approach atleast for starters.
Else, as user said ... you can make a custom view programatically and call it wherever you need that's another approach but it might give you a bit of trouble.
Open to others view if there's one better.
Gluck
I am writing test UI test case for following UI
I want to test on Login click whether I am navigating correctly on Dashboard screen or not.
Is there any method to do this?
My current testing code is like
func testExample() {
let usernameTextField = app.textFields["Username"]
usernameTextField.tap()
usernameTextField.typeText("abc#gmail.com")
let passwordTextField = app.textFields["Password"]
passwordTextField.tap()
passwordTextField.typeText("abc123")
app.buttons["Login" ].tap()
//let loginButton = app.staticTexts["Login"]
//XCTAssertEqual(loginButton.exists, true)
app.navigationBars["UIView"].buttons["Back"].tap()
}
UI Tests can become really fragile when depending on text values. What I encourage you to do is to set the Accessibility Identifier for your ViewController's view. That way, even if you change the title or change the whole layout, you can still be sure you're in the correct Page/Screen/View.
class DashVC: UIViewController {
override func viewDidLoad() {
view.accessibilityIdentifier = "view_dashboard"
}
}
func test_login_withValidInput_goesDashBoard() {
let app = XCUIApplication()
//...
app.buttons["Login" ].tap()
let dashBoardView = app.otherElements["view_dashboard"]
let dashBoardShown = dashBoardView.waitForExistence(timeout: 5)
XCTAssert(dashBoardShown)
}
Try this
app.buttons["Login - Login"].tap()
XCTAssertEqual(app.navigationBars.element.identifier, "appname.CalculationView") //If your second view controller is SecondViewController, your identifier is appname.SecondView.Like that my second view controller is CalculationViewController so my identifier is CalculationView
Try adding an accessibility indicator to the back button so you can check for availability using backButton.exists or backButton.hittable and assert accordingly. In any case, if you set
continueAfterFailure = false
in setUp(), your test will fail as it is if it doesn’t find a button with “Back”.
I'm working on an iPhone app (Objective-C) which has barcode scanning functionality on many of its screens. The user can tap a control to recognize different barcodes and navigate to different screens depending on what type of barcode is recognized. The majority of the logic does not depend on which screen they initiated the scan from... As such, I don't want to duplicate the code in each view controller, but am uncertain where the best place for it is. It requires the user tapping on a detection rectangle, so it does need to be able to handle these events. Many thanks!
You could use a singleton
class DataStore {
static let sharedDataStore = DataStore()
func scanBarcode() {
//logic
}
}
calling the function in another View Controller
class viewController: UIViewController {
let shared = DataStore.sharedDataStore
override func viewDidLoad() {
//calling the function
shared.scanBarcode()
}
I have a progress bar (with its own controller). This bar is supposed to be shown in different views depending on which view is visible. As the progress will be same, If possible I don't want to create many progress bar in many views rather I want to use same instance in all these views. Also in that way when I need to change any property of the progress bar it will be reflected commonly, which is required.
Please suggest me how can I use this common view. And also if my strategy is wrong, what would be the better design for such scenarios.
1) Well you have 2 options. You can declare a new Class ViewBox (or whatever name) and then use that inside your code
First View Controller
var box:ViewBox = ViewBox()
When you segue or transition to your next screen, you can have a predefined variable var box:ViewBox!. Then say when you press a button, the button has a function called transition.
//Now setup the transition inside the Storyboard and name the identifier "toThirdViewController"
override func prepareForSegue(segue:UIStoryboardSegue, sender:AnyObject?) {
if(segue.identifier == "toThirdViewController") {
var vc = segue.destinationViewController as! `nextViewController` //The class of your next viewcontroller goes here
vc.box = self.box
}
//Since The SecondViewController doesn't need ViewBox, we don't need it there.
}
where
nextViewController:UIViewController {
var box:ViewBox!
}
Or you could do a much simpler way and that is to look up a UIPageViewController :)
Looking to create a floating menu in Swift for an iOS application I am developing. Something along the lines of the little red circle menu as shown in the following image.
My initial thoughts were to extend the UIViewController class and add the respective drawing/logic there, however, the application is comprised of a few other controllers, more specifically the UITableViewController which in itself extends UIViewController. Is there perhaps a good place for an extension perhaps? Or is there a more eloquent way of drawing the menu on specific views without the mass duplication of menu related code?
The menu itself will be shown on most screens, so I need to selectively enable it. It'll also be somewhat contextual based on the view/screen the user is currently on.
Any awesome ideas?
You can create your own with the animations and all the things, or you can check this library
https://github.com/lourenco-marinho/ActionButton
var actionButton: ActionButton!
override func viewDidLoad() {
super.viewDidLoad()
let twitterImage = UIImage(named: "twitter_icon.png")!
let plusImage = UIImage(named: "googleplus_icon.png")!
let twitter = ActionButtonItem(title: "Twitter", image: twitterImage)
twitter.action = { item in println("Twitter...") }
let google = ActionButtonItem(title: "Google Plus", image: plusImage)
google.action = { item in println("Google Plus...") }
actionButton = ActionButton(attachedToView: self.view, items: [twitter, google])
actionButton.action = { button in button.toggleMenu() }
}
There is another alternative with this great library :
https://github.com/yoavlt/LiquidFloatingActionButton
You just have to implement the delegate and the dataSource in your ViewController:
let floatingActionButton = LiquidFloatingActionButton(frame: floatingFrame)
floatingActionButton.dataSource = self
floatingActionButton.delegate = self
You could use view controller containment. The menu can be its own view controller with its view laid transparently over top the content view controller.
For example this can be set up in the storyboard by dragging out two container views into a vanilla view controller.