I'm using ActiveLabel to handle URLs in Labels. To open a UIViewController with a WebView to display URL's Content, I want to use a delegate method.
I have tried to different ways to debug my issue.
protocol ActiveLabelURLDelegate {
func activeLabelURLDelegate(url: NSURL)
}
extension FirstVC: ActiveLabelURLDelegate {
func activeLabelURLDelegate(url: NSURL) {
print("debug activeLabelURLDelegate called") // THIS PRINT WILL NOT CALLED
let vc = stb.instantiateViewControllerWithIdentifier("SecondVC") as! SecondVC
vc.webSite = url
self.navigationController?.pushViewController(vc, animated: true)
}
}
class Cell: UITableViewCell {
#IBOutlet weak var myLabel: ActiveLabel!
var labelURLDelegate : ActiveLabelURLDelegate?
override func awakeFromNib() {
myLabel.handleURLTap { givenURL in
print("Success. You just tapped the \(givenURL) URL") // THIS PRINT IS CALLED
self.labelURLDelegate?.activeLabelURLDelegate(givenURL)
}
}
}
After this wasn't working I implemented a helping function to debug what is called.
func openURL(url: NSURL) {
print("debug openURL called") // THIS PRINT IS CALLED AS WELL
labelURLDelegate?.activeLabelURLDelegate(url)
}
Called by:
myLabel.handleURLTap { givenURL in
print("Success. You just tapped the \(givenURL) URL") // STILL CALLED
self.openURL(givenURL)
}
As you can see on the print statements, everything works till the delegate call. The delegate function activeLabelURLDelegate is not called. What am I missing? In the same Cell and same UIViewController, I'm using 4 other delegates for UIButtons and they are working perfectly.
PS: Links will be correctly displayed as Links within the UI & the correct URL is printed to the console.
It looks like you forgot to hook up the delegate. I don't see an assignment to labelURLDelegate in your cell code.
Related
I have a tableview which shows a custom cell.
Inside the cell is a button.
Once the button is clicked, a network call is made and the tableview should reload.
I tried this, but I get Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value at vc.reloadData().
#IBAction func acceptRequestPressed(_ sender: UIButton) {
DatabaseManager.shared.AnswerFriendRequest(emailFriend: friendNameLabel.text!, answer: true) { success in
if success {
let vc = FriendRequestViewController()
vc.reloadData()
}else {
print ("error at answer")
}
}
}
The problem is this line:
let vc = FriendRequestViewController()
After that, vc is the wrong view controller — it is just some view controller living off emptily in thoughtspace, whereas the view controller you want is the already existing view controller that's already in the view controller hierarchy (the "interface").
Few pointers:
You can have a closure upon the data call completion in your class that has the table view.
Eg:
// Custom Table Cell Class
class CustomCellClass: UITableViewCell {
let button = UIButton()
// All other UI set up
// Create a closure that your tableView class can use to set the logic for reloading the tableView
public let buttonCompletion: () -> ()
#IBAction func acceptRequestPressed(_ sender: UIButton) {
DatabaseManager.shared.AnswerFriendRequest(emailFriend: friendNameLabel.text!, answer: true) { [weak self] success in
if success {
// Whatever logic is provided in cellForRow will be executed here. That is, tableView.reloadData()
self?.buttonCompletion()
}else {
print ("error at answer")
}
}
}
}
// Class that has tableView:
class SomeViewController: UIViewController {
private let tableView = UITableView()
.
.
// All other set up, delegate, data source
func cellForRow(..) {
let cell = tableView.dequeueTableViewCell(for: "Cell") as! CustomCellClass
cell.buttonCompletion = {[weak self] in
tableView.reloadData()
}
}
I needed to delegate a click action for my UIView class to my UIViewController class since Swift does not support multiple class inheritance. So i wanted it such that once a button is clicked on my subview, a function in my BrowserViewController class is called.
I am using a protocol to achieve this, but on the function does not triggered when the button is tapped. Please help me out.
View Controller
class BrowseViewController: UIViewController {
var categoryItem: CategoryItem! = CategoryItem() //Category Item
private func setupExplore() {
//assign delegate of category item to controller
self.categoryItem.delegate = self
}
}
// delegate function to be called
extension BrowseViewController: ExploreDelegate {
func categoryClicked(category: ProductCategory) {
print("clicked")
let categoryView = ProductByCategoryView()
categoryView.category = category
categoryView.modalPresentationStyle = .overCurrentContext
self.navigationController?.pushViewController(categoryView, animated: true)
}
}
Explore.swift (subview)
import UIKit
protocol ExploreDelegate: UIViewController {
func categoryClicked(category: ProductCategory)
}
class Explore: UIView {
var delegate: ExploreDelegate?
class CategoryItem: UIView {
var delegate: ExploreDelegate?
var category: ProductCategory? {
didSet {
self.configure()
}
}
var tapped: ((_ category: ProductCategory?) -> Void)?
func configure() {
self.layer.cornerRadius = 6
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.categoryTapped)))
self.layoutIfNeeded()
}
#objc func categoryTapped(_ sender: UIGestureRecognizer) {
delegate?.categoryClicked(category: ProductCategory.everything)
self.tapped?(self.category)
}
}
}
Simply add a print statement inside categoryTapped.
You will then know if it is actually being tapped.
A million things could go wrong, for example, you may have forget to set the UIView to allow intertaction.
After checking that. Next add another print statement inside categoryTapped which shows you whether or not the delegate variable is null.
You'll quickly discover the problem using simple print statements.
print("I got to here!")
It's that easy.
And what about
if delegate == nil { print("it is nil!! oh no!" }
else { print("phew. it is NOT nil.") }
Debugging is really that easy at this level.
Next add a print statement inside setupExplore()
func setupExplore() {
print("setup explore was called")
....
See what happens.
I don't see any piece of code which sets the delegate.
First of all, define delegate as a property inside CategoryItem class, Then you must set the current instance of BrowseViewController to the delegate variable of CategoryItem. Now you can expect your method being called.
There are a few things that could cause the delegate method to not be triggered in this code:
Ensure that isUserInteractionEnabled = true on your CategoryItem. This is probably best done in either the configure() function in the CategoryItem or in the setupExplore() function of the BrowseViewController.
Make sure that the setupExplore() function on the BrowseViewController is being called, and that the category is being set on the CategoryItem to trigger the configure function. Otherwise, either the delegate or the gesture recognizer might not being set.
Side Note - weak vs strong delegate
On a side note, it is usually best practice to make your delegate properties weak var rather that having them be a strong reference, as this makes them prone to strong retain cycles.
Therefore, you might want to consider making the var delegate: ExploreDelegate? on your CategoryItem into weak var delegate: ExploreDelegate?. For more information on this problem, view this post.
I'm using the delegation pattern for my CoreBluetooth based app. I have a main ViewController that is a delegate to my BLEHandler class. I'm updating a button based on the response I get from following delegate method:
func acIsOn(error: NSError?) {
if error == nil{
pushButton.setImage(UIImage(named: "state4"), for: .normal)
}
}
It works fine when my delegate controller class is in the foreground but when I move to another ViewController and calls a method of handler class that in response calls the delegate method above, the button on the image is not updated.
Here's what I have already tried:
Wrapping the statement in DispatchQueue.main.async{}
Calling pushButton.setNeedsLayout() and setNeedsDisplay()
But none of it worked. ,
Also I made sure that this method was being called when the ViewController is not in the foreground.
Edit 1: I'm more interested in learning about the limitation that is not allowing this to happen, I'm not looking for hacks/tricks to bypass this.
Edit 2: As mentioned by Shoazab, button.setBackgroundImage() is working when the VC is in background. Still curious why button.setImage doesn't work in background but it does in when VC is on top.
You are trying to changed an UI element on a ViewController which is not currently displayed. It will update values but no refresh on UI will be executed.
I think that calling SetNeedsDisplay on method viewWillAppear on your ViewController will fix your problem.
You can also use a variable Image and update the button Image when the controller is displayed again.
Move the UI update code to the viewWillAppear like this:
class MainViewController: UIViewController, BLEHandler {
var isUIUpdateNeeded = false
//Define your UI outlets or proeprties
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
if isUIUpdateNeeded {
updateUI()
}
}
func acIsOn(error: NSError?) {
if error == nil{
pushButton.setImage(UIImage(named: "state4"), for: .normal)
}
}
func updateUI() {
//do your UI changes here
pushButton.setImage(UIImage(named: "state4"), for: .normal)
}
}
Call btn.setBackgroundImage() it will set it
Here I'm trying to check unit test cases for view controller.
- I have a view controller with button and label.
- When you click on the button, it will call another method. which feeds the data to the button action label text change.
- I want to check that button triggered that method or not? without adding any boolean or return type of the function.
Here is my code.
class ViewController: UIViewController {
#IBOutlet weak var buttonFetch: UIButton?
#IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func fetchUser() {
self.nameLabel.text = self.getUser()
}
func getUser() ->String {
return User.data()
}
}
struct User {
static func data()->String{
return "Apple"
}
}
Here is my test case
func testFetchUserAction() {
controller.buttonFetch?.sendActions(for: .touchDown)
// I want to test the method getUser from viewcontroller gets called or not
// some thing like this XCTAssert(self.controller.getUser(),"not called")
XCTAssertEqual(self.controller.nameLabel.text!, "Apple")
}
Have you tried as like below..
func testFetchUserAction() {
self.controller.nameLabel.text = nil
//Use this line, If you assigned touchUpInside to your button action
controller.buttonFetch?.sendActions(for: .touchUpInside)
//Use this line, If you assigned touchDown to your button action
controller.buttonFetch?.sendActions(for: .touchDown)
XCTAssert(self.controller.nameLabel.text != nil, "not called")
}
Note: To make test case failed purposely, you can change UIControl.Event in sendActions
What you're missing:
Make sure to call loadViewIfNeeded() somewhere.
This can be in the test, or in setUp(). This will load your outlets, and call viewDidLoad().
Then, as much as possible, test the result of invoking an action, instead of whether or not a method was called. You've done this in your example assertion.
I'd also add that the correct action for buttons is almost always .touchUpInside, not .touchDown. This allows the user to press a button, then drag away to change their mind. "I don't want to push this after all."
Found something similar on another response, attempted something like this?
Source: How to add tap action for button in "Unit Testing" and show Alert
func testTappingLoginButton_ShouldPresentAlert() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let sut = storyboard.instantiateInitialViewController() as! ViewController
sut.loadViewIfNeeded()
let alertVerifier = QCOMockAlertVerifier()
sut.loginButton.sendActions(for: .touchUpInside)
XCTAssertEqual(alertVerifier.presentedCount, 1)
}
Let me know if it works or not!
I have tried like below, its passed the test.
But I'm not sure it is correct or not?
XCTAssertNotNil(self.controller!.getUser, "get user method not called")
Fake tap action on a button for UIViewController testing.
By using this helper, tap(_:), you can verify if your button perform the
#IBAction you were expecting when testing your ViewController.
public func tap(_ button: UIButton) {
button.sendActions(for: .touchUpInside)
}
// Button in your UIViewController
#IBOutlet weak var primaryButton: UIButton!
// In your XCTest subclass
var sut: UIViewController!
tap(sut.primaryButton) // example of use in your test() function
I want to update the label in the DetailViewController everytime I selected a tableRow in the MasterViewController. To achieve this, I designed a delegate, which I have in the MasterVC
protocol TestTableViewControllerDelegate {
func selectedRow(selectedCar : Car)
}
class TestTableViewController: UITableViewController {
...
var delegate : TestTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = DetailViewController()
The delegate works just fine, (it is implemented correctly in the DetailVC), it can pass values from TestTableVC to DetailVC and also correctly do println(), which prints a new Car.model String to the console every time I select a row in the TTVC.
The DetailVC looks like this (shortened):
class DetailViewController: UIViewController, TestTableViewControllerDelegate {
#IBOutlet var textLabel: UILabel!
var theCar : Car? {
didSet(newCar) {
refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
refreshUI()
}
func selectedRow(selectedCar : Car) {
theCar = selectedCar
refreshUI()
}
func refreshUI() {
textLabel?.text = theCar!.model
}
}
I can achieve any kind of action with my delegate, expect for refreshing the UI. I have tried numerous ways, this is my latest attempt. Before that, I tried setting the textLabel's text property directly within the delegate method, didn't work. This problem only occurs when working with the UI-elements. I know it has something to do with the view not being loaded yet, but why does my refreshUI() function not work at all?
I am still a beginner, so any tip or help would be much appreciated!
A workaround I've used is to cerate a properly in the delegate and pass the value to it instead of the UI element. When the view loads I update the label's text properly with the value of the delegates property. I would think there's a better way to do this (I'm new to programming) but this is the best soultion I've come up with so far. Will update with sample code soon.