how to test methods which set private variables in viewcontroller unit testing? - ios

I have a ViewController, which conforms to a protocol. ViewController has the following private UI components:
backgroundImage, logoimage, loginButton , signupButton - all of these are private. (hence not accessible by unit tests)
How can I test the below protocol method implementation in unit testing? I am using XCTestFramework for unit testing.
extension ViewController : ViewControllerProtocol{
func setbackgroundImage(_ image: UIImage) {
backgroundImage.image = image
}
func setLogoImage(_ image: UIImage) {
logoImage.image = image
}
func setLoginButtontitle(_ title: String) {
loginButton.setTitle(title, for: .normal)
}
func setSignupButtonTitle(_ title: String) {
signupButton.setTitle(title, for: .normal)
}
}
protocol ViewControllerProtocol {
func setbackgroundImage(_ image : UIImage)
func setLogoImage(_ image : UIImage)
func setLoginButtontitle(_ title: String)
func setSignupButtonTitle(_ title: String)
func setTitleLabel(_ title: String)
func setContinuewithoutsignupTitle(_ title: String)
}

You can still unit test those values below by adding public getters
protocol ViewControllerProtocol {
func setbackgroundImage(_ image : UIImage)
func setLogoImage(_ image : UIImage)
func setLoginButtontitle(_ title: String)
func setSignupButtonTitle(_ title: String)
func setTitleLabel(_ title: String)
func setContinuewithoutsignupTitle(_ title: String)
func getbackgroundImage() -> UIImage?
func getlogoImage() -> UIImage?
func getloginButtonTitle() -> String?
func getsignupButtonTitle() -> String?
}
class MainTest: XCTestCase {
let viewController = ViewController()
override func setUp() {
super.setUp()
}
func testExample() {
guard let testImage1 = UIImage(named: "testImage1") else {
XCTFail("Could not load test image 1")
return
}
viewController.setbackgroundImage(testImage1)
XCTAssert(viewController.getbackgroundImage() == testImage1)
guard let testImage2 = UIImage(named: "testImage2") else {
XCTFail("Could not load test image 2")
return
}
viewController.setLogoImage(testImage2)
XCTAssert(viewController.getlogoImage() == testImage2)
viewController.setLoginButtontitle("test")
XCTAssert(viewController.getloginButtonTitle() == "test")
viewController.setSignupButtonTitle("test")
XCTAssert(viewController.getsignupButtonTitle() == "test")
}
}

Related

iOS/Xcode: Koloda framework: Unexpectedly found nil while implicitly unwrapping an Optional value

I am trying to implement this Koloda framework into my app. (https://github.com/Yalantis/Koloda). However, when I run my app, I get an error in the line kolodaView.delegate = self which says "Unexpectedly found nil while implicitly unwrapping an Optional value"
I have been trying to debug the code for hours but could not understand where a nil value is coming from. Here is my view controller file below:
import UIKit
import Koloda
import pop
private let numberOfCards: Int = 5
private let frameAnimationSpringBounciness: CGFloat = 9
private let frameAnimationSpringSpeed: CGFloat = 16
private let kolodaCountOfVisibleCards = 2
private let kolodaAlphaValueSemiTransparent: CGFloat = 0.1
class CardViewController: UIViewController {
#IBOutlet weak var kolodaView: CardView!
//MARK: Lifecycle
fileprivate var dataSource: [UIImage] = {
var array: [UIImage] = []
for index in 0..<numberOfCards {
array.append(UIImage(named: "cards_\(index + 1)")!)
}
return array
}()
override func viewDidLoad() {
super.viewDidLoad()
// kolodaView.alphaValueSemiTransparent = kolodaAlphaValueSemiTransparent
// kolodaView.countOfVisibleCards = kolodaCountOfVisibleCards
kolodaView.delegate = self
kolodaView.dataSource = self
// kolodaView.animator = BackgroundKolodaAnimator(koloda: kolodaView)
// self.modalTransitionStyle = UIModalTransitionStyle.flipHorizontal
}
// //MARK: IBActions
// #IBAction func leftButtonTapped() {
// kolodaView?.swipe(.left)
// }
//
// #IBAction func rightButtonTapped() {
// kolodaView?.swipe(.right)
// }
//
// #IBAction func undoButtonTapped() {
// kolodaView?.revertAction()
// }
}
//MARK: KolodaViewDelegate
extension CardViewController: KolodaViewDelegate {
func kolodaDidRunOutOfCards(_ koloda: KolodaView) {
kolodaView.resetCurrentCardIndex()
kolodaView.reloadData()
}
func koloda(_ koloda: KolodaView, didSelectCardAt index: Int) {
let myUrl = "https://yalantis.com/"
if let url = URL(string: "\(myUrl)"), !url.absoluteString.isEmpty {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
func kolodaShouldApplyAppearAnimation(_ koloda: KolodaView) -> Bool {
return true
}
func kolodaShouldMoveBackgroundCard(_ koloda: KolodaView) -> Bool {
return false
}
func kolodaShouldTransparentizeNextCard(_ koloda: KolodaView) -> Bool {
return true
}
func koloda(kolodaBackgroundCardAnimation koloda: KolodaView) -> POPPropertyAnimation? {
let animation = POPSpringAnimation(propertyNamed: kPOPViewFrame)
animation?.springBounciness = frameAnimationSpringBounciness
animation?.springSpeed = frameAnimationSpringSpeed
return animation
}
}
// MARK: KolodaViewDataSource
extension CardViewController: KolodaViewDataSource {
func kolodaSpeedThatCardShouldDrag(_ koloda: KolodaView) -> DragSpeed {
return .default
}
func kolodaNumberOfCards(_ koloda: KolodaView) -> Int {
return numberOfCards
}
func koloda(_ koloda: KolodaView, viewForCardAt index: Int) -> UIView {
return UIImageView(image: UIImage(named: "cards_\(index + 1)"))
}
// func koloda(_ koloda: KolodaView, viewForCardOverlayAt index: Int) -> OverlayView? {
// return Bundle.main.loadNibNamed("CustomOverlayView", owner: self, options: nil)?[0] as? OverlayView
// }
}
If anyone could point out how self.delegate is returning nil, that would be appreciated.
The error states clearly that something is nil and you can't force unwrap in this line:
kolodaView.delegate = self
Here, obviously only kolodaView can produce the crash. It means in the following line the connection to the storyboard was lost. Reconnect to the kolodaView in the storyboard in the following line.
#IBOutlet weak var kolodaView: CardView!

Trying seque data from UIViewController to Weather API

I am trying to pass data from a UIViewController to a SecondViewController that is getting data from a WeatherAPI.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var raceTextField: UITextField!
let races = ["Melbourne", "Bahrain", "Shanghai", "Baku",
"Barcelona", "Monaco", "Montreal","Le Castellet","Speilberg",
"Silverstone","Hockenheimring","Budapest","Spa-Francorchamps","Monza","Singapore","Sochi","Suzuka","Austin","Interlagos","Abu Dhabi"]
var selectedRace: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
createRacesPicker()
createToolbar()
}
#IBAction func sendButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "getWeather", sender: self)
}
func createRacesPicker() {
let racePicker = UIPickerView()
racePicker.delegate = self
raceTextField.inputView = racePicker
}
func createToolbar() {
let toolBar = UIToolbar()
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(ViewController.dismissKeyboard))
toolBar.setItems([doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
raceTextField.inputAccessoryView = toolBar
}
#objc func dismissKeyboard(){
view.endEditing(true)
}
}
//MARK: - UIPickerView Delegate extension
/***************************************************************/
//UIPickerView here
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource{
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component:
Int) -> Int {
return races.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return races[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
selectedRace = races[row]
raceTextField.text = selectedRace
}
//Write the PrepareForSegue Method here
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "getWeather" {
let weatherVC = segue.destination as! WeatherViewController
weatherVC.city = raceTextField.text!
}
}
}
Now what I want to do is update this screen with weather data. Below is my code to update the weatherData.
So basically segue the city chosend from the view controller to the WeatherViewController
import UIKit
import CoreLocation
import Alamofire
import SwiftyJSON
class WeatherViewController: UIViewController {
//constants
let weatherDataModel = WeatherDataModel()
var city = ""
#IBOutlet weak var cityLabel: UILabel!
#IBOutlet weak var weatherIcon: UIImageView!
#IBOutlet weak var temperatureLabel: UILabel!
let WEATHER_URL = "http://api.openweathermap.org/data/2.5/weather"
let APP_ID = "8a99df7bf7ef4a4a202732c392b2d240"
override func viewDidLoad() {
super.viewDidLoad()
cityLabel.text = city
// Do any additional setup after loading the view.
}
//MARK: - Networking
/***************************************************************/
//Write the getWeatherData method here:
func getWeatherData(url: String, parameters: [String : String]) {
Alamofire.request(url, method: .get, parameters: parameters).responseJSON {
response in
if response.result.isSuccess {
print("Success we got the weather data!")
let weatherJSON : JSON = JSON(response.result.value!)
self.updateWeatherData(json: weatherJSON)
} else {
print("Error \(String(describing: response.result.error))")
//self because of the closure. Therefore you have to specify the function.
self.cityLabel.text = "Connection Issues"
}
}
}
//MARK: - JSON Parsing
/***************************************************************/
//updateweatherdata method here
func updateWeatherData(json: JSON) {
if let tempResult = json["main"]["temp"].double {
weatherDataModel.temperature = Int(tempResult - 273)
weatherDataModel.city = json["name"].stringValue
weatherDataModel.condition = json["weather"][0]["id"].intValue
weatherDataModel.weatherIconName = weatherDataModel.updateWeathericon(condition: weatherDataModel.condition)
userEnteredNewCityName(city: city)
updateUIWithWeatherData()
} else {
cityLabel.text = "No data available"
}
}
func userEnteredNewCityName(city: String){
let params : [String : String] = ["q" : city, "appid" : APP_ID]
getWeatherData(url: WEATHER_URL, parameters: params)
}
//Write the updateUIWithWeatherData method here:
func updateUIWithWeatherData() {
cityLabel.text = weatherDataModel.city
temperatureLabel.text = String(weatherDataModel.temperature)
weatherIcon.image = UIImage()
}
}
The weatherDataModel is in a seperate class. So I am trying to add MVC into the app.
I can't seem to see what I am missing. It's probably something really simple. Any help would be appreciated.
You have to call the method to load the data in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
cityLabel.text = city
userEnteredNewCityName(city: city)
}
and delete the line
userEnteredNewCityName(city: city)
in updateWeatherData otherwise you'll run into an infinite loop

Customize FUIAuthPickerViewController

How can I customize the Firebase UI Auth Picker controller with custom buttons, custom actions, background, loader etc..
I already try to subclass the FUIAuthPickerViewController but we can't access to login buttons
This is how you can create your own class of FUIAuthPickerViewController:
Create FUICustomLoginController.swift with:
import UIKit
import FirebaseUI
import FirebaseAuth
class FUICustomLoginController: ViewController {
var authUI: FUIAuth! = FUIAuth.defaultAuthUI()
var auth: Auth = Auth.auth()
private func didSignIn(auth: AuthCredential?, error: Error?, callBack: AuthResultCallback?) {
let callBack: (AuthDataResult?, Error?) -> Void = { [unowned self] result, error in
callBack?(result?.user, error)
self.authUI.delegate?.authUI?(self.authUI, didSignInWith: result, error: error)
}
if let auth = auth {
self.auth.signInAndRetrieveData(with: auth, completion: callBack)
} else if let error = error {
callBack(nil, error)
}
}
func signIn<T: FUIAuthProvider>(type: T.Type, defaultValue: String? = nil) {
try? self.authUI.signOut() // logout from google etc..
self.authUI.providers.first(where: { $0 is T })?.signIn(withDefaultValue: defaultValue, presenting: self, completion: self.didSignIn)
}
}
Subclass your controller from FUICustomLoginController:
class LoginPickerController: FUICustomLoginController {
override func viewDidLoad() {
super.viewDidLoad()
// Customize authUI if needed
//self.authUI.providers = ...
self.authUI.delegate = self
}
#IBAction func loginFacebook(_ sender: Any) {
self.signIn(type: FUIFacebookAuth.self)
}
#IBAction func loginGoogle(_ sender: Any) {
self.signIn(type: FUIGoogleAuth.self)
}
#IBAction func loginPhone(_ sender: Any) {
self.signIn(type: FUIPhoneAuth.self)
}
}
extension LoginPickerController: FUIAuthDelegate {
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?) {
// perform login actions
}
}
You can customize the default buttons, add images etc.. (a working hack )
class SignInViewController: FUIAuthPickerViewController {
weak var delegate: signInProtocol?
// Unhashed nonce.
fileprivate var currentNonce: String?
var backgView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
for each in view.subviews[0].subviews[0].subviews[0].subviews {
if let button = each as? UIButton {
button.layer.cornerRadius = 20.0
button.layer.masksToBounds = true
///do any other button customization here
}
}
///add background image
let scrollView = view.subviews[0]
scrollView.backgroundColor = .clear
let contentView = scrollView.subviews[0]
contentView.backgroundColor = .clear
let background = UIImage(named: "imagename")
let backgroundImageView = UIImageView(image: background)
backgroundImageView.contentMode = .scaleToFill
view.insertSubview(backgroundImageView, at: 0)
}
}

Cannot receive event with custom DelegateProxy and Protocol

I try to migrate delegate of DifficultyViewDelegate to observable. This is my DifficultyViewDelegate :
#objc protocol DifficultyViewDelegate: class {
func levelDidIncrease()
func levelDidDecrease()
}
And my DifficultyView :
weak var delegate: DifficultyViewDelegate?
#IBAction func decreaseLevel(_ sender: Any) {
delegate?.levelDidDecrease()
}
#IBAction func increaseLevel(_ sender: Any) {
delegate?.levelDidIncrease()
}
And this is my RxDifficultyViewDelegateProxy
class RxDifficultyViewDelegateProxy: DelegateProxy, DelegateProxyType {
static func currentDelegateFor(_ object: AnyObject) -> AnyObject? {
let difficultyView: DifficultyView = object as! DifficultyView
return difficultyView.delegate
}
static func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
let difficultyView: DifficultyView = object as! DifficultyView
difficultyView.delegate = delegate as? DifficultyViewDelegate
}
}
I also added an extension on my DifficultyView :
extension DifficultyView {
public var rx_delegate: RxDifficultyViewDelegateProxy {
return RxDifficultyViewDelegateProxy.proxyForObject(RxDifficultyViewDelegateProxy.self)
}
public var rx_levelDidIncrease: Observable<Void> {
return rx_delegate.methodInvoked(#selector(DifficultyViewDelegate.levelDidIncrease)).map { _ in return }
}
}
But it seems that when I do :
difficultyView.rx_levelDidIncrease.asObservable().subscribe(onNext: {
print("did increase")
}).addDisposableTo(disposeBag)
It's never called. Someone has any pointers ?
Try use PublishSubject:
DifficultyView:
class DifficultyView: UIView {
var levelDidIncrease = PublishSubject<Void>()
var levelDidDecrease = PublishSubject<Void>()
#IBAction func decreaseLevel(_ sender: Any) {
levelDidDecrease.onNext()
}
#IBAction func increaseLevel(_ sender: Any) {
levelDidIncrease.onNext()
}
}
And then:
var difficultyView = DifficultyView()
difficultyView.levelDidDecrease.asObservable()
.subscribe(onNext: {
print("did decrease")
})
.addDisposableTo(disposeBag)
difficultyView.decreaseLevel(theSender) // <- THIS line performs the side effect

Accessing a function inside a class function in Swift

How would I call darkness and put parameters in it?
class imageProcesses {
class func DefaultProcesses() {
func CompleteDarkness(image: UIImage) -> UIImage {
let dark = Darken(124, image: image)
return dark
}
}
}
iv'e tried this:
ImageProcessor.DefaultProcesses().CompleteDarkness(image!)
but it doesn't work
You can't call a nested function from outside. Move it out to a class function:
class imageProcesses {
class func CompleteDarkness(image: UIImage) -> UIImage {
let dark = Darken(124, image: image)
return dark
}
class func DefaultProcesses() {
// ...
}
}
Why are you declaring a function inside of a function anyway?
class imageProcesses {
class func DefaultProcesses(image: UIImage) {
completeDarkness(image: image)//calls completeDarkness.
}
func CompleteDarkness(image: UIImage) -> UIImage {
let dark = Darken(124, image: image)
return dark
}
}
Later call
ImageProcessor.DefaultProcesses(image: image!)//Now calling DefaultProcesses will run Complete Darkness and anything else you put in there.
Think this is what you want you can stick that in the play ground and have a go with it
func Darken(input:Int, image:UIImage) -> UIImage{
return UIImage()
}
class ImageProcesses {
class func DefaultProcesses() -> ((UIImage) -> UIImage){
func CompleteDarkness(image: UIImage) -> UIImage {
let dark = Darken(124, image: image)
return dark
}
return CompleteDarkness
}
}
let image = UIImage()
ImageProcesses.DefaultProcesses()(image)

Resources