How to test a UILabel on a programatically added UIView with XCTest - ios

I want to test the .text of my dashboardLabel, but i don't know how to access it via XCTest.
The DashboardView.swift looks like this:
import UIKit
class DashBoardView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
createSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("init(coder:) has not been implemented")
}
// MARK: - Create Subviews
func createSubviews() {
backgroundColor = .white
var dashboardLabel : UILabel
dashboardLabel = {
let label = UILabel()
label.text = "Dashboard Label"
label.textColor = .black
label.frame = CGRect(x:60, y:80, width: 200, height: 30)
label.backgroundColor = .green
label.backgroundColor = .lightGray
label.font = UIFont(name: "Avenir-Oblique", size: 13)
label.textAlignment = .center
return label
}()
}
The DashboardViewController.swift looks like this:
import UIKit
class DashBoardViewController: UIViewController {
var dashboardview = DashBoardView()
//MARK: View Cycle
override func loadView() {
view = dashboardview
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
title = "DashBoard"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I know how to test the Title of the DashboardViewController.swift
import XCTest
#testable import DashBoard
class DashBoardTests: XCTestCase {
func test_if_title_is_DashBoard() {
let vc = DashBoardViewController()
let _ = vc.view
XCTAssertEqual(vc.navigationItem.title, "Dashboard")
}
but i have absolutely no clue, how to access the dashboardLabel on the DashBoardView.swift.
I hope this explains my problem and anyone of you can help me, or point me in the right direction!
Thx ✌️

you can do that using accessibilityIdentifier
Refer : iOS XCUITests access element by accesibility

A fellow Software developer told me a solution which is pretty cool! You need to declare the Label as a property as follows
private(set) var dashboardLabel = UILabel()
and now you are able to access the property within the XCTest. Which makes sense, because you can only test things that are accessible from outside
import UIKit
class DashBoardView : UIView {
private(set) var dashboardLabel = UILabel()
}
XCTestFile
import XCTest
#testable import DashBoard
class DashBoardTests: XCTestCase {
func test_if_dashboard_label_has_title_DashBoard() {
let vc = DashBoardView()
XCTAssertEqual(vc.dashboardLabel.text, "DashBoard")
}
}
Hope that helps!

Related

Custom UIButton background color not working

I have created a custom UIButton to use programmatically in my app. On one screen it works fine. On another, the background does not show up. I have looked up many similar questions and also compared the code to the other View Controller it's used in when it works and there are no obvious reasons. Why is the background color not showing?
The Custom Button Class
import Foundation
import UIKit
class PillButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
initializeButton()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initializeButton()
}
private func initializeButton() {
backgroundColor = UIColor.white
setTitleColor(UIColor(named: "pink"), for: .normal)
contentEdgeInsets = UIEdgeInsets.init(top: 16, left: 48, bottom: 16, right: 48)
translatesAutoresizingMaskIntoConstraints = false
}
override func layoutSubviews() {
super.layoutSubviews()
let height = frame.height / 2
layer.cornerRadius = height
}
}
The View Controller
import Foundation
import UIKit
import MaterialComponents
class EventViewController: BaseViewController {
private static let HORIZONTAL_PADDING: CGFloat = 16
private var confirmButton: PillButton!
private var unableToAttendButton: UILabel!
private var signedUpLabel: UILabel!
private var baseScrollView: UIScrollView!
var event: Event!
private var viewModel: EventViewModel = EventViewModel()
override func viewDidLoad() {
super.viewDidLoad()
createView()
}
override func createView() {
super.createView()
createConfirmButton()
}
private func createConfirmButton() {
confirmButton = PillButton()
let descriptionBottomGuide = UILayoutGuide()
baseScrollView.addSubview(confirmButton)
baseScrollView.addLayoutGuide(descriptionBottomGuide)
descriptionBottomGuide.topAnchor.constraint(equalTo: eventDescription.bottomAnchor).isActive = true
confirmButton.centerXAnchor.constraint(equalTo: baseScrollView.centerXAnchor).isActive = true
confirmButton.topAnchor.constraint(equalTo: descriptionBottomGuide.bottomAnchor, constant: 20).isActive = true
}
}
The code you posted has a LOT of information that you didn't provide, so it's pretty difficult to know what might be going on.
That said, you have a few issues with your PillButton class:
you should not be calling initializeButton in layoutSubviews()
you should update the corner radius in layoutSubviews()
no need to override setTitle
no need to set the layer background color, and you've already set the button's background color so no need to set it again.
Also, in the code you posted, you're not setting the button title anywhere.
Try replacing your PillButton class with this one, and see if you get better results:
class PillButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
initializeButton()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initializeButton()
}
private func initializeButton() {
backgroundColor = Colors.black
setTitleColor(UIColor(named: "pink"), for: .normal)
contentEdgeInsets = UIEdgeInsets.init(top: 16, left: 48, bottom: 16, right: 48)
translatesAutoresizingMaskIntoConstraints = false
}
override func layoutSubviews() {
super.layoutSubviews()
// update corner radius here!
layer.cornerRadius = bounds.height / 2
}
}
If you don't, then you need to do some debugging through the rest of your code (that you have not posted here) to find out what's going on.
confirmButton = PillButton()
I would look into this piece of code. The designated initializers, the ones with frame and coder, in the custom button class call initializeButton(), but you are not implementing init() to do the same.
I would change it to confirmButton = PillButton(frame:)

Add CAGradientLayer to view

I have this custom UIView
import UIKit
class LoginView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupBackground()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupBackground()
}
private func setupBackground() {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [
UIColor(r: 0, g: 17, b: 214).cgColor,
UIColor(r: 0, g: 9, b: 119).cgColor
]
gradientLayer.locations = [0, 1]
gradientLayer.frame = bounds
layer.addSublayer(gradientLayer)
}
}
And this is my UIViewController
import UIKit
class LoginController: UIViewController {
let loginView = LoginView()
override func viewDidLoad() {
super.viewDidLoad()
view = loginView
}
override var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
}
The problem is that the GradientLayer doesn't show on the screen, the background is still black.
Thanks for the help!
If you like to use in viewDidLoad (Load once), just declare your custom view inside like following code:
override func viewDidLoad() {
super.viewDidLoad()
let loginView = LoginView(frame: self.view.bounds)
self.view = loginView
}
You need to init LoginView correctly at right time.
class LoginController: UIViewController {
var loginView : LoginView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
view = LoginView.init(frame: view.bounds)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
}

A Expected Declaration Error when trying to assign a text to label

I'm trying to create a label class which contain other labels.
here is my code
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
class mainLabel: UILabel{
var top: UILabel! = UILabel()
top.text = "text" //*Expected declaration error
var down: UILabel! = UILabel()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
There are several issues with your code. The error you receive is because you can only declare variables or functions in a class scope and using top.text you're trying to modify an instance property of the class outside the function scope, which is not allowed.
Secondly, you shouldn't declare a class inside a function that rarely makes sense.
Lastly, don't declare anything as an implicitly unwrapped optional (UILabel!) if you're assigning a value to it right away.
There are several ways to create a reusable UI element that consists of 2 UILabel and can be created programatically. You can subclass a UIStackView to handle the layout automatically or if you want more control, you could simply subclass UIView, add the 2 UILabels as subViews and handle the layout by adding Autolayout constraints programatically.
Here's a solution using a UIStackView subclass. Modify any properties to fit your exact needs, this is just for demonstration.
class MainLabel: UIStackView {
let topLabel = UILabel()
let bottomLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
axis = .vertical
distribution = .fillEqually
addArrangedSubview(topLabel)
addArrangedSubview(bottomLabel)
topLabel.textColor = .orange
topLabel.backgroundColor = .white
bottomLabel.textColor = .orange
bottomLabel.backgroundColor = .white
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Test in a Playground:
PlaygroundPage.current.needsIndefiniteExecution = true
let mainLabel = MainLabel(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
PlaygroundPage.current.liveView = mainLabel
mainLabel.topLabel.text = "Top"
mainLabel.bottomLabel.text = "Bottom"

Custom UIView without Storyboard

Now I'm practicing build IOS app without using storyboard , but I have a problem want to solve , I created a custom UIView called BannerView and added a background(UIView) and a title(UILabel) , and called this BannerView in the MainVC , but run this app , it crashes at the function setupSubviews() and I don't know why.
import UIKit
import SnapKit
class BannerView: UIView {
var background: UIView!
var title: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setupSubviews()
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupSubviews()
}
func setupSubviews() {
background.backgroundColor = .gray
title.text = "INHEART"
self.addSubview(background)
self.addSubview(title)
}
override func layoutSubviews() {
background.snp.makeConstraints { make in
make.width.equalTo(ScreenWidth)
make.height.equalTo(BannerHeight)
make.left.top.right.equalTo(0)
}
title.snp.makeConstraints { make in
make.width.equalTo(100)
make.center.equalTo(background.snp.center)
}
}
}
class MainVC: UIViewController {
var bannerView:BannerView!
override func viewDidLoad() {
super.viewDidLoad()
bannerView = BannerView(frame: CGRect.zero)
view.addSubview(bannerView)
}
}
Your properties do not appear to be initialised
var background: UIView!
var title: UILabel!
You should initialize these in your init method
self.background = UIView()
self.title = UILabel()
If you use force unwrapping on a class property you must initialize in the init method. XCode should be complaining to you about this and your error message should show a similar error
You are not initialised the background view please initialised them
self.background = UIView()
self.title = UILabel()
and if you want to create custom view by use of xib the follow them Custum View
You must have to initialised the self.background = UIView() and self.title = UILabel() first.
You can initalised them in setupSubviews() function before the set/assign values to them.

UISwitch inside customFooterView needs to call a method outside of class its declared in using Swift

I created a customTableViewFooter programmatically like this:
import UIKit
class CustomFooterView: UIView {
let myLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14, weight: UIFontWeightMedium)
label.textColor = UIColor.black
label.numberOfLines = 1
label.text = "Blah blah blah"
label.textAlignment = .center
return label
}()
let mySwitch: UISwitch = {
let mySwitch = UISwitch()
mySwitch.addTarget(self, action: #selector(switchChanged), for: .valueChanged)
return mySwitch
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
self.layout()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
self.setup()
self.layout()
}
private func setup() {
self.addSubview(myLabel)
self.addSubview(mySwitch)
}
private func layout() {
anonymousLabel.constrainCenterVertically()
anonymousLabel.constrainCenterHorizontally()
anonSwitch.constrainCenterVertically()
anonSwitch.constrainTrailing(at: 20)
}
func switchChanged() {
print("hello")
}
}
Inside my class which implements the UITableView, I add this footer programmatically:
class MyViewController: UIViewController {
var footerView = CustomFooterView()
override func viewDidLoad() {
super.viewDidLoad()
addFooter()
}
func addFooter() {
tableView.tableFooterView = footerView
}
...
}
At the moment, I have the method: switchChanged inside the class where I declare my UISwitch, and what I would like to do, is move this method to my ViewController, so the that the UISwitch, when selected by the user, calls a method inside my ViewController. How would I do this?
This should work (as already suggested in a comment)
footerView.mySwitch.addTarget(self, action: #selector(aSelector), for: .valueChanged)

Resources