How do I use delegates to send data to another view controller and then display it in the collection view? My problem is with moving the array across using delegates.
Below is an example of what I am working on.
When I use usersList in the ThirdViewController, I get an error that says 'Unexpectedly found nil while implicitly unwrapping an Optional value'
protocol ExampleDelegate {
func delegateFunction(usersArray: Array<User>)
}
class ViewController: UIViewController {
private var model: Users = ViewController.createAccount()
var exampleDelegate: ExampleDelegate?
#IBAction func ShowUsers(_ sender: UIButton) {
let ShowUsersVC = storyboard?.instantiateViewController(identifier: "ThirdViewController") as! ThirdViewController
var userList: Array<User> = model.listOfUsers
exampleDelegate?.delegateFunction(usersArray: userList )
present(ShowUsersVC, animated: true)
}
}
class ThirdViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var usersList: Array<User>!
override func viewDidLoad() {
super.viewDidLoad()
let GetUsersVC = storyboard?.instantiateViewController(identifier: "ViewController") as! ViewController
GetUsersVC.showMomentsDelegate = self
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension ThirdViewController: ExampleDelegate {
func delegateFunction(usersArray: Array<User>)
usersList = usersArray
}
You don't need delegates in this case. You are sending data forwards, so just do it like this:
class ViewController: UIViewController {
private var model: Users = ViewController.createAccount()
var exampleDelegate: ExampleDelegate?
#IBAction func showUsers(_ sender: UIButton) {
let showUsersVC = storyboard?.instantiateViewController(identifier: "ThirdViewController") as! ThirdViewController
var userList: Array<User> = model.listOfUsers
showUsersVC.usersList = userList /// pass the data!
present(showUsersVC, animated: true)
}
}
Also in Swift you should lowercase objects like userList, as well as functions like showUsers.
Related
I am trying to make a list of users and their passwords in one view controller, save that information in a dictionary, and send that dictionary to another view controller which asks the user to input their username/password combination to authorize the log in. (the key is the username and the value is the password). Is there a way I can send the dictionary from SecondVC to the FirstVC?
First View Controller
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var Username: UITextField!
#IBOutlet weak var Verification: UILabel!
#IBOutlet weak var Password: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Username.delegate = self
Password.delegate = self
}
var usersDict = [String : String]()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let des = segue.destination as? AccountViewController {
des.usersDict = usersDict
}
}
#IBAction func Authorization(_ sender: Any) {
for ( key , value ) in usersDict{
let v = key.count
var start = 0
if start <= v{
if Username.text == key{
if Password.text == value{
Verification.text = "Looks Good"
}
}
else{
start += 1
}
}
else{
Verification.text = "Yikes"
}
}
}
}
Second View Controller
class AccountViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var CreateUsername: UITextField!
#IBOutlet weak var CreatePassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
CreateUsername.delegate = self
CreatePassword.delegate = self
// Do any additional setup after loading the view.
}
var usersDict = [ String : String ]()
#IBAction func MakeANewAccount(_ sender: Any) {
usersDict[CreateUsername.text!] = CreatePassword.text!
}
}
I have made there dictionary, but it will only send in the beginning and won't update after creating the original account. (dictionary it is sending is empty)
With a segue add this method inside ViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let des = segue.destination as? AccountViewController {
des.usersDict = yourDicHere
}
}
Here's a general pattern for making a controller work with data from some object it creates, in this case a second controller.
Try applying it to your situation and let me know if you run into problems.
protocol Processor {
func process(_ dict: [String : String])
}
class FirstController: UIViewController, Processor {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? SecondController {
controller.delegate = self
} else {
print("Unexpected view controller \(segue.destination)")
}
}
func process(_ dict: [String : String]) {
}
}
class SecondController: UIViewController {
var delegate: Processor?
func someWork() {
if let processor = delegate {
processor.process(["Name" : "Pwd"])
} else {
print("Delegate not assigned")
}
}
}
I'm applying for a junior developer position and I've got a very specific task, that already took me 3 days to complete. Sounds easy - pass data to rootViewController.
That's what I've done:
1)
private func userDefaultsToRootController() {
let input = textField.text!
defaults.set(input, forKey: "SavedLabel")
navigationController?.popViewController(animated: true)
}
private func segueToRootViewController() {
let destinationVC = MainScreen1()
let input = textField.text!
if input == "" { self.navigationController?.popToRootViewController(animated: true) }
destinationVC.input = input
navigationController?.pushViewController(destinationVC, animated: true)
}
private func popToNavigationController() {
let input = textField.text!
if let rootVC = navigationController?.viewControllers.first as? MainScreen1 {
rootVC.input = input
}
navigationController?.popToRootViewController(animated: true)
}
I've used CoreData
But here is the difficult part - I've got an email, that all these methods are not good enough and I need to use delegate and closure. I've done delegation and closures before, but when I popToRootViewController delegate method passes nil. Could you at least point where to find info about this?
** ADDED **
There are 2 View Controllers: Initial and Second one.
That's what I have in the Initial View Controller:
var secondVC = MainScreen2()
override func viewDidLoad() {
super.viewDidLoad()
secondVC.delegate = self
}
That's how I push SecondViewController
#objc private func buttonTapped(_ sender: CustomButton) {
let nextViewController = MainScreen2()
navigationController?.pushViewController(nextViewController, animated: true)
}
In SecondViewController I've got this protocol
protocol PassData {
func transferData(text: String)
}
Also a delegate:
var delegate: PassData?
This is how I go back to initial view controller
#objc private func buttonTapped(_ sender: CustomButton) {
if let input = textField.text {
print(input)
self.delegate?.transferData(text: input)
self.navigationController?.popToRootViewController(animated: true)
}
}
Back to the Initial view controller where I've implemented delegate method
extension MainScreen1: PassData {
func transferData(text: String) {
print("delegate called")
label.text = text
}
}
Delegate doesn't get called.
BASED ON YOUR EDIT:
You must set the delegate in buttonTapped
#objc private func buttonTapped(_ sender: CustomButton) {
let nextViewController = MainScreen2()
nextViewController.delegate = self // HERE WHERE YOU SET THE DELEGATE
navigationController?.pushViewController(nextViewController, animated: true)
}
You can delete the second instance and your code in viewDidLoad. That's not the instance you push.
This should point you in the right direction to use delegation and completion handler.
protocol YourDelegateName {
func passData(data:YourDataType)
}
class SecondViewController: UIViewController {
var delegate: YourDelegateName?
func passDataFromSecondViewController(){
YourCoreDataClass.shared.getCoreData { (yourStringsArray) in
self.delegate?.passData(data: yourStringsArray)
self.navigationController?.popToRootViewController(animated: true)
}
}
class InitialViewController: UIViewController, YourDelegateName {
override func viewDidLoad() {
super.viewDidLoad()
// or whenever you instantiate your SecondViewController
let secondViewController = SecondViewController()
secondViewController.delegate = self //VERY IMPORTANT, MANY MISS THIS
self.navigationController?.pushViewController(createVC, animated: true)
}
func passData(data:YourDataType){
//user your data
}
}
class YourCoreDataClass: NSObject {
static let shared = YourCoreDataClass()
func getCoreData (completion: ([String]) -> ()){
........... your code
let yourStringsArray = [String]() // let's use as example an array of strings
//when you got the data your want to pass
completion(yourStringsArray)
}
}
I am using delegates to get a string value from my modal. When the modal closes I am trying to update Label text using that string. However, I am getting error: Unexpectedly found nil while implicitly unwrapping an Optional value: file. I am not sure how to fix this. I think it's happening because the view is not yet active.
import Cocoa
class ViewControllerA: NSViewController, SomeDelegate {
#IBOutlet weak var msgLabel: NSTextField!
var s: String = "";
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func setDetails(s: String) {
self.user = s;
print("Notified", self.s) // <-- prints: Notified hello again
msgLabel.stringValue = self.s <-- DOESN'T WORK
}
func showModal() -> Void {
msgLabel.stringValue = "hello" // <--- WORKS
let cbvc: NSViewController = {
return self.storyboard!.instantiateController(withIdentifier: "ControllerBVC")
as! NSViewController
}()
self.presentAsModalWindow(cbvc);
}
#IBAction func onBtn(_ sender: Any) {
self.showModal();
}
}
protocol SomeDelegate {
func setDetails(s: String)
}
class ViewControllerB: NSViewController {
#IBOutlet weak var textF: NSTextField!
var delegate: SomeDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
let vc = ViewControllerA()
self.delegate = vc
}
#IBAction func onBtn(_ sender: Any) {
DispatchQueue.main.async {
self.delegate?.setDetails(s: self.textF.stringValue)
self.dismiss("ControllerAVC")
}
}
}
You have a number of problems.
In ViewControllerB.viewDidLoad you are assigning a new instance of ViewControllerA to the delegate property. Don't do that. Your viewDidLoad method should look like this:
override func viewDidLoad() {
super.viewDidLoad()
}
In the showModal method ViewControllerA should assign itself as the delegate on ViewControllerB before ViewControllerB it is presented.
func showModal() -> Void {
let cbvc: NSViewController = {
let vc = self.storyboard!.instantiateController(withIdentifier: "ControllerBVC")
as! ViewControllerB
vc.delegate = self
return vc
}()
self.presentAsModalWindow(cbvc);
}
In the setDetails method just assign the string to your text field directly:
func setDetails(s: String) {
msgLabel.stringValue = s
}
Is use delegate to pass data between three view controllers. I set delegate for both view controllers I want to pass data from to self inside of MapViewController - MapViewController shall receive data from both VCs.
I also added weak var delegate: MapViewController? to both VCs - but somehow it's only working for one of them.
MapViewController:
var newStartItem: MKMapItem?
override func viewDidLoad() {
super.viewDidLoad()
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
searchVC.delegate = self
newVC = (storyboard?.instantiateViewController(withIdentifier: "newLocation") as! NewLocationTableViewController)
newVC.delegate = self
...
}
func addStartAnnotationToMap() {
guard let item = newStartItem else { return }
guard let coordinates = item.placemark.location?.coordinate else { return }
addPin(title: item.name!, subtitle: "", coordinates: coordinates)
}
}
NewLocationViewController: (this one is not working)
class NewLocationTableViewController: UIViewController {
weak var delegate: MapViewController?
...
func passData() {
guard let mapItem = starts?.first else { return }
delegate?.newStartItem = mapItem
delegate?.addStartAnnotationToMap()
}
Xcode won't return any errors either!
You should declare a protocol:
protocol MapViewControllerDelegate: class {
func addStartAnnotationToMap()
}
Then change your delegate variable to:
weak var delegate: MapViewControllerDelegate?
And make MapViewController conform to MapViewControllerDelegate protocol like this:
class MapViewController: UIViewController, MapViewControllerDelegate {
Your delegate must be a reference from the type of your protocol
In this case: weak var delegate: MapViewControllerDelegate?
For this
newVC = (storyboard?.instantiateViewController(withIdentifier: "newLocation") as! NewLocationTableViewController)
newVC.delegate = self
and
weak var delegate: MapViewController?
to work you need to verify that at some point inside the map vc you present that location vc like
self.present(newVC,animated:true,completion:nil)
or push it , plus print it to verify it's not nil and guard doesn't return if starts is empty
func passData() {
print("delegate \(delegate) item \(starts?.first)")
guard let mapItem = starts?.first else { return }
delegate?.newStartItem = mapItem
delegate?.addStartAnnotationToMap()
}
I have been working on this issue for two days now and sadly I cannot figure out the issue to my problem. I'm trying to take one item from my initial one time view controller and send that to my main view controller where it will be saved within the main view controller and will appear upon that controller when reloading the app.
Here is my app delegate code for the "first time" view controller
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if UserDefaults.standard.bool(forKey: "firstTimer") {
let storyBoard = UIStoryboard.init(name: "Main", bundle: nil)
let mainView = storyBoard.instantiateViewController(withIdentifier: "MainViewControllerID")
let nav = UINavigationController(rootViewController: mainView)
nav.navigationBar.isHidden = true
self.window?.rootViewController = nav
}
return true
}
containers and saveContext are default
import UIKit
import CoreData
class FirstTimeViewController: UIViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private var appDelegate = UIApplication.shared.delegate as! AppDelegate
private var player = [Player]()
#IBOutlet weak var nameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// This View Controller will only be used once upon the first time the app is being used.
// MARK: Make func that prepares for segue on initial opening of app
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toMainViewController" {
let mainViewController = segue.destination as! UINavigationController
let destination = mainViewController.topViewController as! MainViewController
if let newPlayer = self.nameTextField.text{
destination.name.name = newPlayer
destination.playerData.name = newPlayer
saveItems()
}
}
}
#IBAction func continueButtonPressed(_ sender: UIStoryboardSegue) {
UserDefaults.standard.set(true, forKey: "firstTimer")
let mainPlayer = PlayerData()
let player1 = Player(entity: Player.entity(), insertInto: context)
player1.name = mainPlayer.name
performSegue(withIdentifier: "toMainViewController", sender: self)
saveItems()
}
func saveItems() {
do {
try context.save()
print("File Successfully saved!")
}catch {
print("Error saving Context \(error)")
}
}
// MARK: Function to Save and Load data??
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
func loadItems() {
let request = Player.fetchRequest() as NSFetchRequest<Player>
do {
player = try context.fetch(request)
print("Info loaded")
} catch {
print("Error fetching data from context \(error)")
}
}
}
MainViewController being sent the information. I only want to send one item and save it to that main view controller.
import UIKit
import Foundation
import CoreData
class MainViewController: UIViewController {
//set up model object, buttons, and labels
// let player: Player!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private var appDelegate = UIApplication.shared.delegate as! AppDelegate
// lazy var nameText = Player(context: context)
// var playerInfo = [Player]()
lazy var player = [Player]()
let playerData = PlayerData()
var name = ""
#IBOutlet weak var playerName: UILabel!
#IBOutlet weak var currentLevel: UILabel!
#IBOutlet weak var xpCounter: UILabel!
#IBOutlet weak var playerProfileImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// loadItems()
// name = playerData.name
if let nameOfPlayer = name.name {
print("This is what we see: \(nameOfPlayer)")
playerName.text = nameOfPlayer
}
appDelegate.saveContext()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// loadView()
}
#IBAction func menuButtonPressed(_ sender: Any) {
}
func loadItems() {
let request = Player.fetchRequest() as NSFetchRequest<Player>
do {
player = try context.fetch(request)
print("Info loaded")
} catch {
print("Error fetching data from context \(error)")
}
}
// MARK : Add Name to Main View
// MARK : Add Xp To Main View
// MARK : Add UI Image to profile image view
// MARK: (Optional) Create a 'Choose a task button to segue to the task tab'
// MARK: Program the Progress Bar to update on xp gained and reset on level up
// MARK: Function to Save and Load data??
}
If dataSource code needed I will add upon request.
Thanks!
Code is wrong. You should do more check
This is what I was able to achieve:
import UIKit
import CoreData
class FirstTimeViewController: UIViewController {
private var player = [Player]()
private var appDelegate = UIApplication.shared.delegate as! AppDelegate
private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#IBOutlet weak var nameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// This View Controller will only be used once upon the first time the app is being used.
// MARK: Make func that prepares for segue on initial opening of app
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toMainViewController" {
let entity = NSEntityDescription.entity(forEntityName: "Player", in: context)
let newPlayer = NSManagedObject(entity: entity!, insertInto: context)
if let newUser = self.nameTextField.text{
newPlayer.setValue(newUser, forKey: "name")
print("This is what i got: ", newPlayer)
}
appDelegate.saveContext()
}
}
#IBAction func continueButtonPressed(_ sender: UIStoryboardSegue) {
UserDefaults.standard.set(true, forKey: "firstTimer")
performSegue(withIdentifier: "toMainViewController", sender: self)
}
And for the Main View Controller:
import UIKit
import Foundation
import CoreData
class MainViewController: UIViewController {
//set up model object, buttons, and labels
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private var appDelegate = UIApplication.shared.delegate as! AppDelegate
lazy var player = [Player]()
#IBOutlet weak var playerName: UILabel!
#IBOutlet weak var currentLevel: UILabel!
#IBOutlet weak var xpCounter: UILabel!
#IBOutlet weak var playerProfileImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
// request.predicate = NSPredicate(format: "name = %#", "noon")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "name") as! String)
self.playerName.text = data.value(forKey: "name") as? String
}
} catch {
print("Failed")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// loadView()
}
#IBAction func menuButtonPressed(_ sender: Any) {
}