Different options to write delegate reference swift - swift

I was trying to create a function delegate to pass data from MainViewController to SecondViewController. I set the protocol:
protocol PassDataDelegate: AnyObject {
func passData(data: [String])
}
I added the delegate function to the SecondViewController:
func passData(data: [String]) {
//Pass Data
}
I set delegate to MainViewController:
weak var delegate: PassDataDelegate?
Here when the button is tapped the delegate function is called:
#objc func buttonTapped() {
guard let vcdelegate = delegate else {return}
vcdelegate.passData(data: data)
}
There are two options to write the delegate reference:
Option 1
I write in MainViewController the reference of delegate to the SecondViewController:
let secondViewController = SecondViewController()
self.delegate = secondViewController
It works like I aspected.
Option 2
I write in SecondViewController:
let vc = MainViewController()
vc.delegate = self
The problem is that delegate is still nil, I don't understand why. Any hints? Thanks

In option 2
let vc = MainViewController()
you create a new instance other than the real presented one so leaving the real delegate = nil

Related

Unable to access property of UITableView subclass

I am trying to test whether the method ReloadData() is called by an instance of UITableView when it's dataSource is updated.
I've created a subclass of UITableView called MockTableView. It has a bool called reloadDataGotCalled which is set to true when the overridden function reloadData() is called. I then try access that property from within my test class to test whether it is true.
However when I try to do so the compiler gives me the message that "Value of type 'UITableView' has no member 'reloadDataGotCalled'"
I'm not sure why it's doing that, because as far as I can see I've set that value to be of the type 'MockTableView' which should have that member?
// A ViewController that contains a tableView outlet that I want to test.
class ItemListViewController: UIViewController {
let itemManager = ItemManager()
#IBOutlet var tableView: UITableView!
#IBOutlet var dataProvider: (UITableViewDataSource & UITableViewDelegate & ItemManagerSettable)!
#IBAction func addItem(_ sender: UIBarButtonItem) {
if let nextViewController = storyboard?.instantiateViewController(identifier: "InputViewController") as? InputViewController {
nextViewController.itemManager = itemManager
present(nextViewController, animated: true, completion: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = dataProvider
tableView.delegate = dataProvider
dataProvider.itemManager = itemManager
}
}
// My test class
class ItemListViewControllerTest: XCTestCase {
var sut: ItemListViewController!
override func setUp() {
//Given
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(identifier: "ItemListViewController")
sut = (viewController as! ItemListViewController)
//When
sut.loadViewIfNeeded()
}
// The test where I'm trying to assign sut.tableView to mockTableView
func test_TableView_IsReloadedWhenItemAddedToItemManger() {
let mockTableView = MockTableView()
sut.tableView = mockTableView
let item = ToDoItem(title: "Foo")
sut.itemManager.add(item)
sut.beginAppearanceTransition(true, animated: true)
sut.endAppearanceTransition()
XCTAssertTrue(sut.tableView.reloadDataGotCalled) // <- this is where I'm getting the compiler message "Value of type 'UITableView' has no member 'reloadDataGotCalled'"
}
}
// My mockTableView subclass in an extension of the ItemListViewControllerTests
extension ItemListViewControllerTest {
class MockTableView: UITableView {
var reloadDataGotCalled = false
override func reloadData() {
super.reloadData()
reloadDataGotCalled = true
}
}
}
I'm expecting that it should compile, and then the test should fail because I've not written the code to make it pass yet?
You have defined tableView instance in ItemListViewController as UITableView. So, you can't access the MockTableView's property with that instance.
You can only access the parent's properties from the children not the vice versa. If you still want to access the property you can try something like the snippet below.
XCTAssertTrue((sut.tableView as! MockTableView).reloadDataGotCalled)
Hope it helps.

Can a UIViewController that is presented as a popover be its own popoverPresentationController delegate?

In the project shown below there is an InitialViewController that has a single button labeled "Show Popover". When that button is tapped the app is supposed to present the second view controller (PopoverViewController) as a popover. The second view controller just has a label saying "Popover!".
This works fine if the InitialViewController takes care of instantiating PopoverViewController, retrieving the popoverPresentationController and then setting the popoverPresentationController's delegate to itself (to InitialViewController). You can see the result, below:
For maximum reusability, however, it would be great if the InitialViewController did not need to know anything about how the presentation controller is delegated. I think it should be possible for the PopoverViewController to set itself as the popoverPresentationController's delegate. I've tried this in either the viewDidLoad or the viewWillAppear functions of the PopoverViewController. However, the PopoverViewController is presented modally in both cases, as shown below:
All the code is contained in just the InitialViewController and the PopoverViewController. The code used in the failing version of the InitialViewController is shown below:
import UIKit
// MARK: - UIViewController subclass
class InitialViewController: UIViewController {
struct Lets {
static let storyboardName = "Main"
static let popoverStoryboardID = "Popover View Controller"
}
#IBAction func showPopoverButton(_ sender: UIButton) {
// instantiate & present the popover view controller
let storyboard = UIStoryboard(name: Lets.storyboardName,
bundle: nil )
let popoverViewController =
storyboard.instantiateViewController(withIdentifier: Lets.popoverStoryboardID )
popoverViewController.modalPresentationStyle = .popover
guard let popoverPresenter = popoverViewController.popoverPresentationController
else {
fatalError( "could not retrieve a pointer to the 'popoverPresentationController' property of popoverViewController")
}
present(popoverViewController,
animated: true,
completion: nil )
// Retrieve and configure UIPopoverPresentationController
// after presentation (per
// https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller)
popoverPresenter.permittedArrowDirections = .any
let button = sender
popoverPresenter.sourceView = button
popoverPresenter.sourceRect = button.bounds
}
}
The code in the failing PopoverViewController is shown below:
import UIKit
// MARK: - main UIViewController subclass
class PopoverViewController: UIViewController {
// MARK: API
var factorForMarginsAroundButton: CGFloat = 1.2
// MARK: outlets and actions
#IBOutlet weak var popoverLabel: UILabel!
// MARK: lifecycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear( animated )
// set the preferred size for popover presentations
let labelSize =
popoverLabel.systemLayoutSizeFitting( UILayoutFittingCompressedSize )
let labelWithMargins =
CGSize(width: labelSize.width * factorForMarginsAroundButton,
height: labelSize.height * factorForMarginsAroundButton )
preferredContentSize = labelWithMargins
// set the delegate for the popoverPresentationController to self
popoverPresentationController?.delegate = self
}
}
// MARK: - UIPopoverPresentationControllerDelegate
// (inherits from protocol UIAdaptivePresentationControllerDelegate)
extension PopoverViewController: UIPopoverPresentationControllerDelegate
{
func adaptivePresentationStyle(for controller: UIPresentationController,
traitCollection: UITraitCollection)
-> UIModalPresentationStyle{
return .none
}
}
Is it possible for a view controller that is being presented as a popover to be the delegate for its own popoverPresentationController?
I'm using Xcode 8.0, Swift 3.1 and the target is iOS 10.0
It's certainly possible. You're dealing with a timing issue. You need to set the delegate before viewWillAppear. Unfortunately, there is no convenient view lifecycle function to insert the assignment into, so I did this instead.
In your PopoverViewController class, assign the delegate in an overriden getter. You can make the assignment conditional if you'd like. This creates a permanent relationship, so other code code never "override" the delegate by assigning it.
override var popoverPresentationController: UIPopoverPresentationController? {
get {
let ppc = super.popoverPresentationController
ppc?.delegate = self
return ppc
}
}
As #allenh has correctly observed, you need to set the delgate before viewWillAppear, and he has offered a clever solution by setting the delegate by overriding the popoverPresentationController getter.
You could also set the delegate to the popover itself in your showPopover() function between setting modalPresentationStyle and presenting the popover:
let vc = storyboard.instantiateViewController(withIdentifier: Lets.popoverStoryboardID )
vc.modalPresentationStyle = .popover
vc.popoverPresentationController?.delegate = vc
present(vc, animated: true, completion: nil)

How to bring value from a UIviewController using delegate without instantiating object

I have 3 UIViewControllers say:
v1ViewController
v2ViewController
v3ViewController.
I have pushed controllers as v2 on v1 and v3 on v2.
Now I want to bring some value back to v1ViewController from v3ViewController using delegates.
On v3Viewcontroller I wrote it:
for vc in self.navigationController!.viewControllers{
if vc is v1ViewController{
delegate?.returnFilteredImage(imageView.image!)
self.navigationController?.popToViewController(vc, animated: true)
}
}
How can I use delegates because in v1ViewController I haven't create object of v3Viewcontroller; consequently I cannot connect delegate to self.
So how can i do that.
1. Using delgates
create a protocol and implement in your firstviewcontroller
protocol My {
func returnFilteredImage(image: UIImage)
}
class FirstViewController: UIViewcontroller, My {
...
func returnFilteredImage(image: UIImage) {
}
}
and in your thirdViewController create a property and assign FirstViewController delegate to this.
class ThirdViewController: UIViewController {
var delegate: My?
...
override func viewDidLoad() {
super.viewDidLoad()
for vc in self.navigationController!.viewControllers{
if vc is FirstViewController {
let vc1 = vc as! FirstViewController
self.delegate = vc1
self.delegate?.returnFilteredImage(imageView.image!)
self.navigationController?.popToViewController(vc, animated: true)
}
}
}
}
2. Using local notifications
check here
You can do this using local notifications
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//add observer
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.didgetImage(_:)), name: "receiveImageNotification", object: nil)
}
func didgetImage(notification: NSNotification) {
if let image = notification.userInfo?["image"] as? UIImage {
// do something with your image
}
}
}
and from third view controller, first notification
let imageDataDict:[String: UIImage] = ["image": image]
NSNotificationCenter.defaultCenter().postNotificationName("receiveImageNotification", object: self, userInfo: imageDataDict)
Hope this helps :)

Having issues setting delegate with Observer Pattern

I'm trying to realize the Observer Pattern and I'm experiencing some difficulty as my delegate doesn't seem to be setting properly.
In my Main.storyboard I have a ViewController with a container view. I also have an input box where I'm capturing numbers from a number keypad.
Here's my storyboard:
I'm trying to implement my own Observer Pattern using a protocol that looks like this:
protocol PropertyObserverDelegate {
func willChangePropertyValue(newPropertyValue:Int)
func didChangePropertyValue(oldPropertyValue:Int)
}
My main ViewController.swift
class ViewController: UIViewController {
#IBOutlet weak var numberField: UITextField!
// observer placeholder to be initialized in implementing controller
var observer : PropertyObserverDelegate?
var enteredNumber: Int = 0 {
willSet(newValue) {
print("//Two: willSet \(observer)") // nil !
observer?.willChangePropertyValue(5) // hard coded value for testing
}
didSet {
print("//Three: didSet")
observer?.didChangePropertyValue(5) // hard coded value for testing
}
}
#IBAction func numbersEntered(sender: UITextField) {
guard let inputString = numberField.text else {
return
}
guard let number : Int = Int(inputString) else {
return
}
print("//One: \(number)")
self.enteredNumber = number // fires my property observer
}
}
My ObservingViewController:
class ObservingViewController: UIViewController, PropertyObserverDelegate {
// never fires!
func willChangePropertyValue(newPropertyValue: Int) {
print("//four")
print(newPropertyValue)
}
// never fires!
func didChangePropertyValue(oldPropertyValue: Int) {
print("//five")
print(oldPropertyValue)
}
override func viewDidLoad() {
super.viewDidLoad()
print("view loads")
// attempting to set my delegate
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
print("//six \(pvc)")
pvc.observer = self
}
}
Here's what my console prints:
What's happening
As you can see when my willSet fires, my observer is nil which indicates that I have failed to set my delegate in my ObservingViewController. I thought I set my delegate using these lines:
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
print("//six \(pvc)")
pvc.observer = self
However, I must be setting my delegate incorrectly if it's coming back nil.
Question
How do I properly set my delegate?
You are calling into the storyboard to instantiate a view controller and setting it as the observer, however that instantiates a new instance of that view controller, it doesn't mean that it is referencing the one single "view controller" that is in the storyboard. ObservingViewController needs another way to reference the ViewController that has already been created.
So #Chris did reenforce my suspicions which helped me to figure out a solution for assigning my delegate to my view controller properly.
In my ObservingViewController I just need to replace the code in my viewDidLoad with the following:
override func viewDidLoad() {
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let vc = app.window?.rootViewController as! ViewController
vc.observer = self
}
Rather than creating a new instance of my view controller, I'm now getting my actual view controller.

Reload MainViewController when PopViewController dismissed

I have a main view controller and pop up controller.Please refer screen shots.
Code :
#IBAction func show(sender: AnyObject) {
var popView = popupviewcontroller(nibName:"popview",bundle:nil)
var popController = UIPopoverController(contentViewController: popView)
popController.popoverContentSize = CGSize(width: 450, height: 450)
popController.presentPopoverFromRect(sender.frame, inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Down, animated: true)
}
For the popupViewcontoller i used .xib.
When press save button data saved to core data.
Lets come to my problem, in my mainViewController i fetched data and fill them in dynamically created lables.That occurred when view load.I want to reload mainViewController when close button form popViewController pressed.
I tried within the close button my code are here, i just tried to reload the mainVc :
var mainVC = mainviewcontroller()
#IBAction func close(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
//mainVc.viewDidLoad()
mainVC.reloadInputViews()
}
Does not give output.
Conclusion : I want a way to refresh view controller from another view in swift.
Thanks in advance.
Using UITableView only we can reload the data!,So we have to use table view custom cell textfield or Label. Now we can able to reload our custom textfield data.
Does your main ViewController use a UITableView?
You can use viewWillAppear and get the data again and use tableView.reloadData() to reload the data.
EDIT:
with var mainVC = mainviewcontroller() you're just making a new instance of your MainViewController. If you want to use reloadInputViews(), you can put it in viewWillLoad.
You should use protocol for it. You can read more about protocols here https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
You can make protocol in PopViewController
protocol PopViewControllerProtocol {
static var valuesChanged()
}
Later you should implement this protocol in MainViewController and refresh data.
Example:
File UIPopoverController.swift
protocol UIPopoverControllerDelegate{
func valuesChanged(changedValue:String)
}
class UIPopoverController: UIViewController {
var delegate: UIPopoverControllerDelegate! = nil
#IBAction func close(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
//mainVc.viewDidLoad()
mainVC.valuesChanged("some value")
}}
File MainViewController.swift
class MainViewController: UIViewController, UIPopoverControllerDelegate
{
func valuesChanged(changedValue:String) {
//this will be called when popuviewcontroller call valueschanged on delegate object
}
#IBAction func show(sender: AnyObject) {
var popView = popupviewcontroller(nibName:"popview",bundle:nil)
var popController = UIPopoverController(contentViewController: popView)
popController.delegate = self;
popController.popoverContentSize = CGSize(width: 450, height: 450)
popController.presentPopoverFromRect(sender.frame, inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Down, animated: true)
}
}
With God Grace i found a solution that is , just create a Global variable
Var fetchedArray = Nsarray()
Then Write the following code in the popovoer controller save button
func save(){
// Write codes for saving data
let request = NSFetchRequest(entityName: "Enitity name")
request.returnsObjectsAsFaults = false
let Total = try? context.executeFetchRequest(request)
if let wrapResults = Total {
fetchedArray = wrapResults
}
}
Then fill the Labels by using fetchedArray.
Thanks
Swift 2, try
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle
viewDidLoad()
return.None
Works for me