In my HomeViewController I have a text field and a button. User can write a city or touch in this button to find a city. When button is touched, a CitiesTableView shows up by segue (present modally). When choose a city of the table list, I want to send this selected city to inside the text field in HomeViewController.
class HomeViewController: UIViewController{
#IBOutlet weak var tfSearchCity: UITextField!
//Button with Segue to present Modally CitiesTableViewController
var cities = [Cities]()
}
// Protocol to receive data
extension HomeViewController: CitieFinderDelegate{
func addCity(city: Cities){
tfSearchCity.text = city.City
print(city)
}
}
}
I create a Protocol to pass data between views but is not working.
protocol CitieFinderDelegate {
func addCity(city: Cities)
}
// this TableView are presenting Modally
class CitiesTableViewController: UITableViewController {
var cities: [Cities] = []
var delegate: CitieFinderDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let city = cities[indexPath.row]
let findCity = Cities(UF: city.UF, City: city.City)
delegate?.addCity(city: findCity)
}
// MARK: - Action to close the view
#IBAction func close(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
}}
It seems that the delegate hasn't been assigned to HomeViewController's instance.
Make sure you have the below code in your HomeViewController
override func prepare(for segue: UIStoryboardSegue, sender _: Any?) {
if segue.identifier == "segue identifier name",
let citiesViewController = segue.destination as? CitiesTableViewController {
// cities assigned
citiesViewController.delegate = self
}
}
Related
I'm trying to set UIbutton's text in firstVC as selectedCity variable when it selected in tableViewCell in SecondVC . I cannot use segue or tabBarController. Updated photo of ui
FirstVC
import UIKit
class homeView: UIViewController{
#IBOutlet weak var selectRegion: UIButton!
}
SecondVC
import UIKit
class selectCityPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var selectedCity: String!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0{
selectedCity = "Almaty"
}
if indexPath.row == 1{
selectedCity = "Усть-Каменогорск"
}
dismiss(animated: true, completion: nil)
// when this view controller is dismissed, uibutton's text in next viewcontroller should equal to selectedCity
}
}
You can use delegation. These are the required steps:
Create a delegate protocol that defines the messages sent to the delegate.
Create a delegate property in the delegating class to keep track of the delegate.
Adopt and implement the delegate protocol in the delegate class.
Call the delegate from the delegating object.
SecondVC
import UIKit
//1. Create a delegate protocol
protocol CitySelectionDelegate {
func pickCity(with selectedCity:String)
}
class SelectCityPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var selectedCity: String!
//2. Create a delegate property in the delegating class
var delegate:CitySelectionDelegate?
//other stuff
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
if indexPath.row == 0{
selectedCity = "Almaty"
}
if indexPath.row == 1{
selectedCity = "Усть-Каменогорск"
}
4. Call the delegate from the delegating object.
delegate?.pickCity(with: selectedCity) //call your delegate method
//dismiss(animated: true, completion: nil) when you dismiss is up to you
}
}
HomeViewController
UPDATE: Since you have another VC embedded inside a UINavigationController, both the Home and Select Region Page MUST conform to the delegate and you have to set the delegate inside prepareForSegue method.
// 3. Adopt and implement the delegate protocol
class HomeViewController: UIViewController, CitySelectionDelegate{
#IBOutlet weak var selectRegion: UIButton!
func pickCity(with selectedCity: String) {
self.selectRegion.text = selectedCity
}
/*please pay attention. In this case you must reference the
navigation controller first and the your can get the right
instance of the delegate variable inside your firstVC (I called
firstVC but in your case is Select Region Page*/
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// you MUST set the right identifier
if segue.identifier == "showFirst" {
if let navController = segue.destination as? UINavigationController {
if let firstVC = navController.topViewController as? FirstViewController{
firstVC.delegate = self
}
}
}
}
}
since you have another VC (embedded in a navigation controller), also this one must conform to the delegate like so:
class FirstViewController: UIViewController, CitySelectionDelegate {
var delegate: CitySelectionDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
func pickCity(with selectedCity: String){
// here you simply call the delegate method again and you dismiss the navigation controller
self.delegate?.pickCity(with: selectedCity)
self.navigationController?.dismiss(animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecond" {
if let controller = segue.destination as? SelectCityPage {
controller.delegate = self
}
}
}
you can use delegate or block or notification when secVC action to change the firstVC value you needed.
Or you set a property in secVC and pass the value in firstVC you want to change, So you can change value passed from firstVC in secVC.
I have a view controller called ListViewController and another called AddFoodViewController. In ListViewController users are able to add their own ingredients to the grocery list which is presented in a table view. When they go to the AddFoodViewController, users should be able to click a button which says "add to list" which will add the array of ingrediets (that are already presented in a table view) into the grocery list. I am new to this, so I was wondering if anyone can help? I have successfully been able to get the ListViewController to work, however I am not sure how to add the array of ingredients from AddFoodViewController into the previous ListViewController.
class AddFoodViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var FoodTableView: UITableView!
#IBOutlet weak var sendFoodBtn: UIButton!
//array of food
let array = ["1 Salad", "3oz Chicken", "2 Tomatoes", "2 Cucumbers"]
let category = ""
override func viewDidLoad() {
super.viewDidLoad()
}
//display array in table view
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return(array.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let foodCell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "foodCell")
foodCell.textLabel?.text = array[indexPath.row]
foodCell.backgroundColor = .clear
foodCell.textLabel?.textColor = .darkGray
foodCell.textLabel?.font = UIFont(name: (foodCell.textLabel?.font.fontName)!, size:17)
return foodCell
}
//button that is supposed to add all ingredients to the ListViewController
#IBAction func addOnClick(_ sender: Any) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "toList"){
let vc = (segue.destination as! ListViewController)
vc.category = array
}
}
}
Passing data to previous view controller can be implemented by delegation pattern, first at all, declare a protocol in your AddFoodViewController, and define a delegate property in view controller.
// AddFoodViewController.swift
protocol AddFoodViewControllerDelegate {
func addIngredient(array: [String])
}
class AddFoodViewController: UIViewController {
...
var delegate: AddFoodViewControllerDelegate?
// MARK: add function
func actionAdd() {
delegate?addIngredient(array)
}
...
}
Back to your ListViewController,find the segue destination view controller which is your AddFoodViewController (remember to assign its class name in the storyboard), and assign delegate to self.
// ListViewController.swift
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
...
let vc = segue.destination as? AddFoodViewController
vc?.delegate = self
...
}
// in the same file, implement delegate method here
extension ListViewController: AddFoodViewControllerDelegate {
func addIngredient(array: [String]) {
items += array
// do table reload or something
}
}
I am trying to perform a segue from a UITableView with news. If you push one of the news, it performs a segue to the specific news you selected.
It is easy and I have done it a few times... but I don't know what am I doing wrong this time.
The NewsDetailViewController is like this:
class NewsDetailViewController: UIViewController {
#IBOutlet weak var newsImage: UIImageView!
#IBOutlet weak var newsTitle: UILabel!
#IBOutlet weak var newsDate: UILabel!
#IBOutlet weak var newsText: UILabel!
var newsLink: String?
override func viewDidLoad() {
super.viewDidLoad()
// Hides the navigation bar.
self.navigationController?.setNavigationBarHidden(true, animated: false)
}
#IBAction func closeNews(_ sender: UIButton) {
navigationController?.popViewController(animated: true)
}
}
And the segue in the NewsTableViewController is like this:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you selected the row: \(indexPath.row)")
tableView.deselectRow(at: indexPath, animated: true)
self.performSegue(withIdentifier: "goToNewsDetail", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToNewsDetail" {
if let destination = segue.destination as? NewsDetailViewController {
destination.newsLink = "whateverlink"
destination.newsTitle.text = "whatevertitle"
}
}
}
And the line: destination.newsLink = "whateverlink"
Works perfectly.
But the line: destination.newsTitle.text = "whatevertitle"
Throws a
fatal error: Unexpectedly found nil while implicitly unwrapping an
Optional value.
And I have no idea of what if going on. The same problem happens when trying to initialise the rest of the labels in the destination.
This line is the problem
destination.newsTitle.text = "whatevertitle"
don't access outlets of the destination vc as they not yet loaded send a string and assign it to the label in the destination vc
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToNewsDetail" {
if let destination = segue.destination as? NewsDetailViewController {
destination.newsLink = "whateverlink"
destination.toSend = "whatevertitle"
}
}
}
class NewsDetailViewController: UIViewController {
#IBOutlet weak var newsTitle:UILabel!
var toSend = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.newsTitle.text = toSend
}
}
In the prepareForSegue method, the newsTitle label is still not initialised, so it is nil.
Generally, you shouldn't set the target VC's view's properties in prepareForSegue. You should declare a newsTitleText property in NewsDetailViewController:
var newsTitleText: String!
And set this property instead:
destination.newsTitleText = "whatevertitle"
Then set the newsTitle.text in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
// Hides the navigation bar.
self.navigationController?.setNavigationBarHidden(true, animated: false)
newsTitle.text = newsTitleText
}
When the prepare(for:sender:) method is called, NewsDetailViewController has not loaded the view yet so you can't set the text on a label. What you want to do is create another property on NewsDetailViewController such as var newsTitleText: String?. Then in viewDidLoad you can call newsTitle.text = newsTitleText.
I'm trying to pass data, using delegate between two VC, but I can't get why it's not working.
My first VC
class ViewController: UIViewController {
#IBOutlet weak var profileImage: UIImageView!
func downloadPhoto() {
self.performSegue(withIdentifier: "showPhotoFromInternet", sender: nil)
}
}
extension ViewController: IPresentaionPhotoFormInternetDelegate {
func setNewImage(imageToShow: UIImage) {
profileImage.image = imageToShow
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? PresentPhotoFromInternetViewController {
destination.delegate = self
}
}
My second VC
class PresentPhotoFromInternetViewController: UIViewController {
var imageToSend: UIImage?
var delegate: IPresentaionPhotoFormInternetDelegate?
#IBOutlet weak var photoCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
photoCollectionView.allowsMultipleSelection = false
}
#IBAction func sendPhotoToPreviousController(_ sender: Any) {
delegate?.setNewImage(imageToShow: iamgeToSend!)
performSegue(withIdentifier: "sendPhoto", sender: nil)
}
extension PresentPhotoFromInternetViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! photoCollectionViewCell
print("Cell is selected")
iamgeToSend = cell.photoImageView.image
cell.selectedView.backgroundColor = UIColor.green
}
protocol IPresentaionPhotoFormInternetDelegate {
func setNewImage(imageToShow: UIImage)
}
I use present modaly segue for from the first VC to the second, and show form the second to the first
When I perform segue from the second VC there is no updates in my first one, although it passes all breakpoints.
The problem is instead of popping controller you are loading completely new controller in your button action
#IBAction func sendPhotoToPreviousController(_ sender: Any) {
delegate?.setNewImage(imageToShow: iamgeToSend!)
//Comment or remove the performSegue
//performSegue(withIdentifier: "sendPhoto", sender: nil)
//Now simply pop this controller
_ = self.navigationController?.popViewController(animated: true)
// If you are presenting this controller then you need to dismiss
self.dismiss(animated: true)
}
Note: If you are segue is kind of Push then use popViewController or is it kind of Modal than you need to use dismiss.
i'm trying to start writing Swift and i'm trying to get a value from a modal view controller with no luck.
I have two controllers, the ViewController and modalViewController.
In ViewController i have a UITableView and with a press of a button i open the modalViewController.
Then from a UITextField i pass the value.
I have implement a protocol with delegate and func but somewhere i'm missing something or had it wrong.
ViewController.swift
import UIKit
class ViewController: UIViewController,UITableViewDelegate,modalViewControllerDelegate{
#IBOutlet var table: UITableView!
var tableData = ["First Row","Second Row","Third Row"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
table.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(table:UITableView?,numberOfRowsInSection section: Int) -> Int
{
return tableData.count
}
func tableView(table:UITableView?,cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
{
let cell:UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Default,reuseIdentifier:"cell")
cell.textLabel?.text = tableData[indexPath.row]
return cell
}
func sendText(text: NSString) {
tableData.append(text)
} }
modalViewController.swift
import UIKit
protocol modalViewControllerDelegate {
func sendText(var text: NSString)
}
class modalViewController: UIViewController{
let delegate: modalViewControllerDelegate?
#IBOutlet var textField: UITextField?
#IBAction func cancelButton(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func saveButton(sender: AnyObject) {
delegate?.sendText(self.textField!.text)
dismissViewControllerAnimated(true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
I have no errors in the code, the delegate is not working, it's always nil.
Thanks.
You have to assign the delegate in your first view controller.
Also, you have to change let delegate: modalViewControllerDelegate? to a var, or else you can't change it.
Right now your delegate is empty.
It's unclear how you're accessing ModalViewController. If you're using segues:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "modalViewControllerSegue" {
var destination = segue.destinationViewController as CategoryViewController
destination.delegate = self
}
}
Or if you're doing it programmatically:
var modalViewController = ModalViewController(parameters)
modalViewController.delegate = self
presentViewController(modalViewController, animated: true, completion: nil)
Storyboard identifier:
let destination = UIStoryboard.mainStoryboard().instantiateViewControllerWithIdentifier("ModalViewController") as ModalViewController
delegate = self
showViewController(destination, sender: nil)
EDIT:
If you want to access ModalViewController by selecting a cell you need the tableView: didSelectRowAtIndexPath method:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("modalViewControllerSegue", sender: self)
}
Using this, you'll need the method prepareForSegue to set the delegate.
You have to set your modalViewController's delegate property before presenting it. If you're using segues, you can do this in prepareForSegue(_:).
Also, class names should begin with uppercase letters (modalViewController should be ModalViewController). Only instances should begin with lowercase letters.
Another option, instead of using delegation, is to use an unwind segue. Here's a tutorial: http://www.cocoanetics.com/2014/04/unwinding/
In your case, in your presenting view controller you could have the method:
func returnFromModalView(segue: UIStoryboardSegue) {
// This is called when returning from the modal view controller.
if let modalVC = segue.sourceViewController as? ModalViewController
where segue.identifier == "mySegueID" {
let text = modalVC.textField.text
// Now do stuff with the text.
}
}
And then just link up everything in the Interface Builder as shown in the tutorial.