Swift: What instance of my class is in the view - ios

I'm building an app with a container view holding a tableView controller. I create this tableView, but I don't know how to access this object again so I can call function on it. Currently there is a BucketTableViewController object being created automatically (maybe from the storyboard). Then later I want to call a function on it and create another BucketTableViewController object. I can verify they are unique with print statement on that method. How do I set a variable for an object that is the original object?
import UIKit
class FirstViewController: UIViewController {
var bigArray = ["M", "A", "R", "C"]
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
reachForWebsite()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func reachForWebsite(){
let url = NSURL(...)
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
do {
...
// HERE IS THE ISSUE
var bucketsVC = BucketTableViewController()
bucketsVC.updateBuckets(self.bigArray)
} catch let myJSONError {
print(myJSONError)
}
}
task!.resume()
}
}

You can grab a reference to it from prepareForSeque(_:sender:) in the view controller that owns the container. Make sure that identifier matches the name of the identifier you've set on the segue from the storyboard in Interface Builder. Or you can omit the identifier part if you know for certain that there are no other segues with destination's of type BucketTableViewController.
class BucketTableViewController: UITableViewController {}
class FirstViewController: UIViewController {
var bucketViewController: BucketTableViewController!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue( segue, sender: sender )
if let vc = segue.destinationViewController as? BucketTableViewController where segue.identifier == "embeddedBuketViewcontroller" {
self.bucketViewController = vc
}
}
}

A comment is too tight for this, so I'm making it an answer. You can make bucketsVC` an instance variable:
class FirstViewController: UIViewController {
var bucketsVS : BucketTableViewController?
func reachForWebsite(){
...
do {
self.bucketsVC = BucketTableViewController()
self.bucketsVC!.updateBuckets(self.bigArray)
} catch {
...
}
// Now you can use it anywhere within your UIViewController
}
}

Related

Passing data to detailVC in MVP architecture

I have two view controllers. I want to pass data from mainVC to detailVC during preparing segue. What's better option: calling and pass data to secondVCpresenter (which update View) or directly pass data to secondVC?
class MainVC: UIViewController {
var dataToSend = [Data]
.
.
.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? DetailVC {
destVC.detailPresenter.setData(data: dataToSend)
}
class DetailVC: UIViewController {
lazy var detailPresenter = DetailPresenter(detailDelegate: self)
var newData = [Data]
extension DetailVC: DetailDelegate {
func setData(data: [Data]) {
newData = data
}
}
protocol DetailDelegate: class {
func setData(data: [Data])
}
class DetailPresenter {
weak var detailDelegate DetailDelegate?
init(detailDelegate: DetailDelegate) {
self.detailDelegate = detailDelegate
}
func setData(data: [Data]) {
detailDelegate?.setData(data: data)
}
}
I wonder if it is ok to call detailPresenter from prepare segue in MainVC and if it is not too dirty way to send data?
I agree with Kudos, in this case there's no need for a delegate to pass data to DetailVC as it's already referenced. Using a delegate for this has made it unnecessarily complicated. So yes calling detailPresenter is quite a dirty way of doing things.

How to set a delegate in Swift

I want to send my UserModel with all user informations from a ViewController (ShowUserViewController) to another ViewController (ChatViewController) with a delegate but its not working.
In my ShowUserViewControllers user are all informations I want to send to the ChatViewController.
var user: UserModel?
In my ChatViewController I have the following declaration where I want to send my datas:
var currentUser: UserModel?
Here my protocol:
protocol UserInfoToChatID {
func observeUserID(user: UserModel)
}
Here I prepare the segue and set delegate by tapping the button:
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
var delegate: UserInfoToChatID?
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
delegate?.observeUserID(user: user!)
}
At last I call the delegate in my ChatViewController:
extension ChatViewController: UserInfoToChatID {
func observeUserID(user: UserModel) {
self.currentUser = user
performSegue(withIdentifier: "UserInfoToChatVC", sender: self)
}
}
If you need to pass data from one ViewController to another, you don't have to use delegates for this. You can just pass this data as sender parameter of performSegue method:
performSegue(withIdentifier: "UserInfoToChatVC", sender: user!)
then in prepare for segue just downcast sender as UserModel and assign destination's currentUser variable
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = sender as! UserModel
}
}
But in your case you actually don't have to pass user as sender. You can just assign destination's currentUser variable as ShowUserViewController's global variable user
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = user!
}
}
2 things:
first, if you just want to pass data from one viewController to other viewController you don't need to use delegate pattern, just pass the object to the next viewController on prepare form segue.
second, if you want to implement the delegate pattern you should have one viewController than call to the delegate and the other implement the functions.
example:
protocol ExampleDelegate: class {
func delegateFunction()
}
class A {
//have delegate var
weak var delegate: ExampleDelegate?
// someWhere in the code when needed call to the delegate function...
delegate?.delegateFunction()
}
Class B: ExampleDelegate {
func delegateFunction() {
// do some code....
}
//when you move to the next viewControoler(to A in that case)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AClass" {
if let vc = segue.destination as? A {
vc.delegate = self
}
}
}
To pass the UserModel object forward, from ShowUserViewController to ChatViewController, you should use something called Dependency Injection:
So you'll do something like this inside ShowUserViewController:
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "UserInfoToChatVC", sender: nil)
}
Note: The sender parameter should be the object that initiated the segue. It could be self, i.e. the ShowUserViewController object, but I'd advise against passing the UserModel object, because that object did not initiate the segue, and has nothing to do with navigation at all. It should be injected inside the Destination Controller later on.
In the same file, override the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
I believe you've mostly done this part right, but you may need to communicate back from ChatViewController to ShowUserViewController.
In that case, you can and should use Delegation.
Create something like this inside ShowUserViewController:
protocol ChatViewControllerDelegate: class {
func didUpdateUser(_ model: UserModel)
}
class ChatViewController: UIViewControler {
var user: UserModel?
weak var delegate: ChatViewControllerDelegate?
/* more code */
func someEventHappened() {
delegate?.didUpdateUser(self.user!)
}
}
Finally, there is an additional line to be added to the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
// Add this line...
chatVC.delegate = self
}
}
And specify that the ShowUserViewController implements the ChatViewControllerDelegate protocol, then override the didUpdateUser(_:) method:
func didUpdateUser(_ model: UserModel) {
// Some code here
}

Clean Swift - Routing without segues

I found Router in Clean Swift architecture is responsible to navigate and pass data between view controllers. Some samples and articles depict that Routers use segue to communicate with view controllers. What would be the convenient design when I don't want to use any segue from Storyboard. Is it possible to pass data without segue in Clean Swift? If you describe with simplest complete example, would be appreciated.
Article says that you can:
// 2. Present another view controller programmatically
You can use this to manually create, configure and push viewController.
Example.
Let's pretend that you have ViewController with button (handle push):
final class ViewController: UIViewController {
private var router: ViewControllerRouterInput!
override func viewDidLoad() {
super.viewDidLoad()
router = ViewControllerRouter(viewController: self)
}
#IBAction func pushController(_ sender: UIButton) {
router.navigateToPushedViewController(value: 1)
}
}
This ViewController has router that implements ViewControllerRouterInput protocol.
protocol ViewControllerRouterInput {
func navigateToPushedViewController(value: Int)
}
final class ViewControllerRouter: ViewControllerRouterInput {
weak var viewController: ViewController?
init(viewController: ViewController) {
self.viewController = viewController
}
// MARK: - ViewControllerRouterInput
func navigateToPushedViewController(value: Int) {
let pushedViewController = PushedViewController.instantiate()
pushedViewController.configure(viewModel: PushedViewModel(value: value))
viewController?.navigationController?.pushViewController(pushedViewController, animated: true)
}
}
The navigateToPushedViewController func can takes any parameter you want (it is good to encapsulate parameters before configure new vc, so you may want to do that).
And the PushedViewController hasn't any specific implementation. Just configure() method and assert (notify you about missing configure() call):
final class PushedViewModel {
let value: Int
init(value: Int) {
self.value = value
}
}
final class PushedViewController: UIViewController, StoryboardBased {
#IBOutlet weak var label: UILabel!
private var viewModel: PushedViewModel!
func configure(viewModel: PushedViewModel) {
self.viewModel = viewModel
}
override func viewDidLoad() {
super.viewDidLoad()
assert(viewModel != nil, "viewModel is nil. You should call configure method before push vc.")
label.text = "Pushed View Controller with value: \(viewModel.value)"
}
}
Note: also, i used Reusable pod to reduce boilerplate code.
Result:
As above article explained you can use option 2/3/4 of navigateToSomewhere method as per your app design.
func navigateToSomewhere()
{
// 2. Present another view controller programmatically
// viewController.presentViewController(someWhereViewController, animated: true, completion: nil)
// 3. Ask the navigation controller to push another view controller onto the stack
// viewController.navigationController?.pushViewController(someWhereViewController, animated: true)
// 4. Present a view controller from a different storyboard
// let storyboard = UIStoryboard(name: "OtherThanMain", bundle: nil)
// let someWhereViewController = storyboard.instantiateInitialViewController() as! SomeWhereViewController
// viewController.navigationController?.pushViewController(someWhereViewController, animated: true)
}
You need pass data across protocols
protocol SecondModuleInput {
// pass data func or variable
var data: Any? { get set }
}
protocol SecondModuleOutput {
// pass data func or variable
func send(data: Any)
}
First presenter
class FirstPresenter: SecondModuleOutput {
var view: UIViewController
var secondModuleInputHandler: SecondModuleInput?
// MARK: SecondModuleInput
func send(data: Any) {
//sended data from SecondPresenter
}
}
Second presenter
class SecondPresenter: SecondModuleInput {
var view: UIViewController
var secondModuleOutputHandler: SecondModuleOutput?
static func configureWith(block: #escaping (SecondModuleInput) -> (SecondModuleOutput)) -> UIViewController {
let secondPresenter = SecondPresenter()
secondPresenter.secondModuleOutputHandler = block(secondPresenter)
return secondPresenter.view
}
// Sending data to first presenter
func sendDataToFirstPresenter(data: Any) {
secondModuleOutputHandler?.send(data: data)
}
// MARK: FirstModuleInput
var data: Any?
}
Router
class FirstRouter {
func goToSecondModuleFrom(firstPresenter: FirstPresenter, with data: Any) {
let secondPresenterView = SecondPresenter.configureWith { (secondPreseter) -> (SecondModuleOutput) in
firstPresenter.secondModuleInputHandler = secondPreseter
return firstPresenter
}
//Pass data to SecondPresenter
firstPresenter.secondModuleInputHandler?.data = data
//Go to another view controller
//firstPresenter.view.present(secondPresenterView, animated: true, completion: nil)
//firstPresenter.view.navigationController.pushViewController(secondPresenterView, animated: true)
}
}

How can I call a method that is inside a UIViewController embedded in a container from a parent UIViewController?

I have an ios app in swift and I have a UIViewController (let's call it parentController) with a container. This container embeds another UIViewController called embedController.
embedController contains a method that prints a message to a console.
How can I call this method from my parentController?
I tried to use protocols, my current code is as follows:
class ParentController: UIViewController {
var handleEmbedController:HandleEmbedController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "embedViewSegue"){
if let embed = segue.destinationViewController as? EmbedController {
embed.value1 = value1
}
}
#IBAction func sendMsgButtonAction(sender: AnyObject) {
handleEmbedController?.printMsg() //this so far does nothing
}
}
and my embedController:
protocol HandleEmbedController: class {
func printMsg()
}
class EmbedController: UITableViewController, HandleEmbedController{
var value1 = ""
func printMsg(){
print("printing some embedded message")
}
}
How can I print this message from a parent controller?
What are you doing in your prepare for segue? Aren't you supposed to set your delegate (protocol) there? Like this:
if (segue.identifier == "embedViewSegue"){
if let embed = segue.destinationViewController as? EmbedController {
self.handleEmbedController = embed
}
}
If you put a breakpoint in sendMsgButtonAction you should see that the property handleEmbedController is nil. And that's why the method call does nothing since you are safely unwrapping it with ?.

Could not Cast Value problems Swift iOS

I have an app where I ask the user to login first. After a successful login I want the username to follow to all the other view controllers in the app. I have tried declaring this as a public variable, but I am not sure how to do that properly (ie. it is not working in any instance) so I am now trying a prepareForSegue approach. However when I do i get a "Could not cast value of type 'SwiftLoginScreen.LoginVC' (0x10cdd33a0) to 'SwiftLoginScreen.ActionVC' (0x10cdd3130). (lldb)" - error.
I want to be able to call up the username from any and all view controllers in the app, but I am not able to solve this. I have the username from my HomeVC and I now want it to pass on to my ActionVC.
Home VC:
import UIKit
class HomeVC: UIViewController {
#IBOutlet var usernameLabel : UILabel!
var currentUser : NSString!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
let isLoggedIn:Int = prefs.integerForKey("ISLOGGEDIN") as Int
if (isLoggedIn != 1) {
self.performSegueWithIdentifier("goto_login", sender: self)
} else {
self.usernameLabel.text = prefs.valueForKey("USERNAME") as? String
currentUser = prefs.valueForKey("USERNAME") as? String
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as! ActionVC
destinationVC.operatingUser = self.currentUser;
}
#IBAction func gotoAction(sender : UIButton) {
let appDomain = NSBundle.mainBundle().bundleIdentifier
NSUserDefaults.standardUserDefaults().removePersistentDomainForName(appDomain!)
self.performSegueWithIdentifier("goto_action", sender: self)
}
}
Action VC:
import Foundation
import UIKit
class ActionVC : UIViewController, UIImagePickerControllerDelegate{
#IBOutlet var usernameLable : UILabel!
var operatingUser : NSString!
override func viewDidLoad() {
super.viewDidLoad()
self.usernameLable.text = operatingUser as? String
}
prepareForSegue will get called for all segues involving the View Controller. In your case it is being called for a segue where the destination view controller is an instance of LoginVC, but you have a forced downcast (as!) to ActionVC - so you get an exception at runtime.
You can use an optional binding to make sure that you are handling the correct segue -
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destinationVC = segue.destinationViewController as? ActionVC {
destinationVC.operatingUser = self.currentUser;
}
}
Your problem can be solved by adding
a username variable in AppDelegate class.
If you have a variable named userName
in AppDelegate you can access it anywhere in your app without passing it.
class AppDelegate
{
var userName = "Admin"
}
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
In your first controller you can set
the value:
appDelegate.userName = "Value"
and use the same to access it from
another controller:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.userName

Resources