I have four view controllers and I am trying to send data between controllers via protocols although my two other protocols is working fine the last one is not working and I could not figure it out.
This is my first view controller that should send the data:
import UIKit
class ExercisesTableViewController: UITableViewController, ExerciseProtocol{
var exerciseToSend: Exercise? {
didSet{
print(exerciseToSend!) // This prints the result.
performSegue(withIdentifier: "showDetail", sender: self)
}
}
func getExercise() -> Exercise {
return exerciseToSend!
}
.
.
.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
exerciseToSend = exercises[indexPath.row]
}
.
.
.
}
This is the controller that should receieve the data
import UIKit
protocol ExerciseProtocol {
func getExercise() -> Exercise
}
class ExerciseDetailViewController: UIViewController {
var delegate:ExerciseProtocol?
override func viewDidLoad() {
print(delegate?.getExercise()) // This doesn't print the result.
super.viewDidLoad()
}
}
Add this inside ExercisesTableViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let des = segue.destination as? ExerciseDetailViewController else {return}
des.delegate = self
}
Related
protocol WeatherManagerDelegate {
func didUpdateWeater(weather: ConsolidatedWeather)
func didFailWithError(error: Error)
}
ViewController: where i am setting value didSelectRowAt and using performSegue going to another viewController
class WeatherListViewController: UIViewController {
var delegate: WeatherManagerDelegate?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let index = weatherViewModel.didSelect(at: indexPath.row)
self.delegate?.didUpdateWeater(weather: index)
performSegue(withIdentifier: K.DetailsView.segueIndentifier, sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVc = segue.destination as! DetailsViewController
}
}
this is my ViewModel calss: from my ViewModel, I will send value to ViewController and Update UI
class DetailsWeatherViewModel{
var w = WeatherListViewController()
func a(){
print("aaa")
w.delegate = self
}
}
extension DetailsWeatherViewModel: WeatherManagerDelegate{
func didUpdateWeater(weather: ConsolidatedWeather) {
weatherData = weather
print("weatherData: \(String(describing: weatherData))")
}
func didFailWithError(error: Error) {
print(error)
}
}
what I am doing wrong...????
You should be careful of memory leaks when using delegate pattern. I think you can solve this problem by making protocol limit to class and declare property by weak var. Although WeatherListViewController disappeared, WeatherListViewController and DetailsWeatherViewModel are not likely to be deinit unless you use weak reference. Try this.
protocol WeatherManagerDelegate : class {
func didUpdateWeater(weather: ConsolidatedWeather)
func didFailWithError(error: Error)
}
weak var delegate: WeatherManagerDelegate?
If you are following MVVM architecture then you can create a viewModel object inside your viewcontroller and then use the updated values in VM directly using VM object.
Else if you want to use delegate then you need to write the protocols in viewModel and use it in VC. You shouldn't be creating Viewcontroller object inside the Viewmodel.
This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 2 years ago.
My project is to create a split view where master is TableViewController and Detail is WebKit. My main problem is that when I pass an Url from my TableView it turns out as hill while unwrapping it in Webkit Controller. I also use struct List with variables name:Strin? and url: URL!
Here is my code for tableViewController:
import UIKit
import WebKit
class WebBrowsers: UITableViewController {
private var list:[List] = [
List(name: "google.com", url: URL(string:"google.com"))
]
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return list.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
cell.textLabel?.text = list[indexPath.row].name
// Configure the cell...
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail"{
if let destination = segue.destination as? infoBrowser{
if let row = tableView.indexPathForSelectedRow?.row{
destination.detailUrl = list[row].url
}
}
}
}
}
here is my webkit ViewController
import UIKit
import WebKit
class infoBrowser: UIViewController {
var detailUrl: URL!
#IBOutlet weak var showPage: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let request = URLRequest(url: detailUrl!)
showPage.load(request)
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
}
}
Issue is with this line of code:
if let row = tableView.indexPathForSelectedRow?.row{
destination.detailUrl = list[row].url
}
tableView.indexPathForSelectedRow? will always be nil as there is no implementation for the didSelectRow delegate for tableView.
Either shift your navigation code to didSelect of tableView or implement this just to select row.
URL(string:"google.com") this returns an optional URL which means, if the String passed is not a valid url then it will return nil and in your infoBrowser you have declared detailUrl as implicit optional which means when it tries to access this variable while creating URLRequest, if the value is nil it will crash,
so change var detailUrl: URL? also before creating URL request add a check to ensure URL is not nil
override func viewDidLoad() {
super.viewDidLoad()
guard let url = detailUrl else { return }
let request = URLRequest(url: url)
showPage.load(request)
}
I have spent a fair bit of time on this and still can't seem to figure out what I'm doing wrong.
I would like to run the reloadData() function in my own function on another view controller than the tableViewController but I get the error Type 'HomeViewController' has no member 'reloadData'.
HomeViewController:
import UIKit
import CoreData
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var index = ""
var item : [ListItem] = [] //listName
var listName = [""]
override func viewDidLoad() {
super.viewDidLoad()
self.homeListsTableView.delegate = self
self.homeListsTableView.dataSource = self
homeListsTableView.reloadData()
//List Names
//List Items - Within a list Name
let listItem1 = ListItem()
listItem1.name = "" //Update so names are updated via append the ListItem Array
listItem1.location = ""
item.append(listItem1)
let listItem2 = ListItem()
listItem2.name = ""
listItem2.location = ""
item.append(listItem2)
}
#IBOutlet weak var homeListsTableView: UITableView!
#IBAction func templatesButton(_ sender: Any) {
tabBarController?.selectedIndex = 2
}
override func viewWillAppear(_ animated: Bool) {
if let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext {
if let coreDataListItems = try? context.fetch(ListName.fetchRequest()) as? [ListName] {
print(coreDataListItems)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = listName[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// performSegue(withIdentifier: "goToItems", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! ListNameViewController
vc.homeListsTableViewVC = homeListsTableView
}
}
ListName View Controller:
import UIKit
import CoreData
class ListNameViewController: UIViewController, UITableViewDelegate {
// var listName = [""]
let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
var homeListsTableViewVC = HomeViewController.self
override func viewDidLoad() {
super.viewDidLoad()
}
#IBOutlet weak var listNameValue: UITextField!
#IBOutlet weak var locationOption: UITextField!
#IBOutlet weak var createButtonChange: UIButton!
#IBAction func createButton(_ sender: Any) {
let newList = ListName(context: context!)
newList.listName = listNameValue.text
// let location = locationOption.text!
//tabBarController?.selectedIndex = 0
//performSegue(withIdentifier: "home", sender: nil)
}
func saveList() {
do {
try context!.save()
} catch {
print("Error saving context \(error)")
}
homeListsTableViewVC.reloadData()
}
}
I'm trying to use the prepare for segue function to pass the homeListsTableView data to then use the reloadData() function. Could someone tell me what I'm doing wrong?
Thanks!
You can use delegate or closure to update your tableview.. here is pointers of how you can use closure in your classes to update view
class ListNameViewController: UIViewController, UITableViewDelegate {
var callback: (()->())?
//....
func saveList() {
do {
try context!.save()
} catch {
print("Error saving context \(error)")
}
callback?()
}
}
And while creating ListNameViewController
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! ListNameViewController
vc.callback = { [weak self] in
self?.homeListsTableView.reloadData()
}
}
}
I am trying to make an app using the GoogleBooks API where I can use either a title or author or both to search for books. I am currently just working on the delegate portion to be able to pass the search terms to the results table view. However, I am getting errors with the variables I am using being let constants but I have them declared as var so I'm not sure where I am messing up.
This is the UIViewController code for the view with the two search boxes and the button:
import UIKit
protocol ViewControllerDelegate: class {
func searchInput(_ titleFromSearch: String?, authorFromSearch: String?)
}
class ViewController: UIViewController {
#IBOutlet weak var titleFromSearch: UITextField!
#IBOutlet weak var authorFromSearch: UITextField!
weak var delegate: ViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
titleFromSearch.delegate = self
authorFromSearch.delegate = self
}
override func touchesEnded(_ touches: Set<UITouch>, with event:
UIEvent?) {
super.touchesEnded(touches, with: event)
titleFromSearch.resignFirstResponder()
authorFromSearch.resignFirstResponder()
}
}
extension ViewController: UITextFieldDelegate {
func fieldsDidEndEditing(_ titleEntered: UITextField, authorEntered:
UITextField) {
if let delegateController = delegate {
delegateController.searchInput(titleFromSearch.text,
authorFromSearch: authorFromSearch.text)
}
}
}
And this is the code for the TableViewController that I have set up for the results to be displayed in.
import UIKit
import GoogleBooksApiClient
class SearchResultsTableViewController: UITableViewController {
var titleFromSearch: String?
var authorFromSearch: String?
var data = [Volume]()
override func viewDidLoad() {
super.viewDidLoad()
let session = URLSession.shared
let client = GoogleBooksApiClient(session: session)
let req = GoogleBooksApi.VolumeRequest.List(query: "Google")
let task: URLSessionDataTask = client.invoke(
req,
onSuccess: { [weak self] volumes in
self?.data = volumes.items
self?.tableView.reloadData()
},
onError: { error in
print("\(error)") }
)
task.resume()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell",
for: indexPath)
let item = data[indexPath.row]
cell.textLabel?.text = item.volumeInfo.title
cell.detailTextLabel?.text = item.volumeInfo.authors.first
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let destination = segue.destination as? ViewController {
destination.delegate = self
}
}
}
Here is where I get the let constant error is with these two assignment statements:
extension SearchResultsTableViewController: ViewControllerDelegate {
func searchInput(_ titleFromSearch: String?, authorFromSearch: String?)
{
titleFromSearch = titleFromSearch
authorFromSearch = authorFromSearch
}
}
I added all the code because as I said I am new to iOS and I'm not sure where this error stems from in the code.
In the two lines causing your errors:
titleFromSearch = titleFromSearch
authorFromSearch = authorFromSearch
you are attempting to assign the parameters to themselves. You want to set the parameter values to your properties of the same name. To do this, add self. to the property references:
self.titleFromSearch = titleFromSearch
self.authorFromSearch = authorFromSearch
Here is the code with the delegate process suggested...
in main view controller...
protocol FilterDelegate: class {
func onRedFilter()
func onGreenFilter()
func onBlueFilter()
func onUnfiltered()
}
class ViewController: UIViewController, FilterDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
----
// Increase red color level on image by one.
func onRedFilter() {
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "filterSegue" {
let dest = segue.destinationViewController as! CollectionViewController
dest.filterDelegate = self
}
}
in collection view controller...
var filterDelegate: FilterDelegate?
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("Cell \(indexPath.row) selected")
guard let filterDelegate = filterDelegate else {
print("Filter delegate wasn't set!")
return
}
switch indexPath.row {
case 0:
filterDelegate.onRedFilter()
case 1:
filterDelegate.onGreenFilter()
case 2:
filterDelegate.onBlueFilter()
case 3:
filterDelegate.onUnfiltered()
default:
print("No available filter.")
}
Right now...the code stops at the guard block and prints the error message. The switch block is not executed on any press of a cell.
Your theory in your second last sentence is correct - when you call storyboard.instantiateViewControllerWithIdentifier in the "child" view controller, you are actually creating an entirely new instance of your main view controller. You are not getting a reference to the existing main view controller, which is why the methods you're calling are not having any effect.
There are several ways to achieve what you're trying to do, including the delegate pattern or using closures. Here's a sketch of what it could look like using a delegate protocol:
protocol FilterDelegate: class {
func onRedFilter()
func onGreenFilter()
func onBlueFilter()
func onUnfiltered()
}
class MainViewController: UIViewController, FilterDelegate {
// implement these as required
func onRedFilter() { }
func onGreenFilter() { }
func onBlueFilter() { }
func onUnfiltered() { }
// when we segue to the child view controller, we need to give it a reference
// to the *existing* main view controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let dest = segue.destination as? ChildViewController {
dest.filterDelegate = self
}
}
}
class ChildViewController: UIViewController {
var filterDelegate: FilterDelegate?
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// ...
guard let filterDelegate = filterDelegate else {
print("Filter delegate wasn't set!")
return
}
switch indexPath.row {
case 0:
filterDelegate.onRedFilter()
case 1:
filterDelegate.onGreenFilter()
case 2:
filterDelegate.onBlueFilter()
case 3:
filterDelegate.onUnfiltered()
default:
print("No available filter.")
}
}
}
Another option would be to provide closures on ChildViewController for every function on MainViewController that the child needs to call, and set them in prepareForSegue. Using a delegate seems a bit cleaner though since there are a bunch of functions in this case.