I was reading the CoreData docs here and came across the following example illustrating how to implement a segue from a parent list to a child using dependency injection and was a little confused by the code sample given.
class DetailViewController: UIViewController {
weak var employee: AAAEmployeeMO?
}
and in the MasterViewController
let CellDetailIdentifier = "CellDetailIdentifier"
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier! {
case CellDetailIdentifier:
let destination = segue.destinationViewController as! DetailViewController
let indexPath = tableView.indexPathForSelectedRow!
let selectedObject = fetchedResultsController.objectAtIndexPath(indexPath) as! AAAEmployeeMO
destination.employee = selectedObject
default:
print("Unknown segue: \(segue.identifier)")
}
}
I've got a decent understanding of weak variables but am a bit confused with this particular case because I don't see how it is necessary in this scenario.
Can anyone enlighten me as to why it is used here? Where is the potential for a strong reference cycle?
Related
I'm working on my first iOS app with Xcode and Swift. My app has multiple UIViewControllers (=VC). Everything works quite well, but there is an error which I couldn't work out on my own with the help of Google or StackOverflow.
My problem is:
I have a UIButton called 'Quit' in the VC 'CompleteTest' which I control dragged in the Main storyboard to the VC 'Main Menu' in order that it brings the user back to the 'Main Menu' when it is pressed.
But when I click this UIButton in the Simulator the error:
Thread 1 signal SIGABRT comes up.
This is not the first time I stumbled over this error and I saw multiple threads in stack overflow giving a solution to this problem. In the past by checking the outlets I could solve this error quite easily.
But in this case this solution doesn't work, because the outlets look fine (there are no exclamation-points showed).
What is even more strange is where the error is showed.
Normally the SIGABRT error is shown in the AppDelegate, if I'm not mistaken.
But in my case it is shown at the line 54 of the VC 'CompleteTest'-file. At this line I am transferring data to the next VC 'Final Complete test' with the override function 'prerpare'.
Unfortunately, I couldn't put images in this question, even tough
I prepared them. Anyway I hope my problem is still clear.
Furthermore, excuse me for my poor English as it is my second language.
How should I proceed? Thank you in advance for your help!
import UIKit
class CompleteTest: UIViewController {
// Components of Visualisation VC
#IBOutlet weak var ShotNumber: UILabel!
#IBOutlet weak var ProjectedScore: UILabel!
#IBOutlet weak var Distance: UILabel!
var sum: Double = 100.0 //sum represents overall Score of Test
#IBOutlet weak var Report: UIButton!
var help: Int = 0 // var 'help' is helping to change the number showed on the Scoring Buttons in VC
#IBOutlet weak var Left1: UIButton!
#IBOutlet weak var Right1: UIButton!
#IBOutlet weak var Center1: UIButton!
#IBOutlet weak var BadContact1: UIButton!
//Percentage components
var leftpct: Int = 0
var rightpct: Int = 0
var centerpct: Int = 0
var totalpct: Int = 0
var badcontactpct: Int = 0
// Transfer of data from ViewController 'CompleteTest' to ViewController 'FinalCompleteTest'
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let finalCompleteTest = segue.destination as! FinalCompleteTest
finalCompleteTest.score = String(sum)
finalCompleteTest.Leftpct = Int(leftpct)
finalCompleteTest.Rightpct = Int(rightpct)
finalCompleteTest.Centerpct = Int(centerpct)
finalCompleteTest.Totalpct = Int(totalpct)
finalCompleteTest.BadContactpct = Int(badcontactpct)
}
override func viewDidLoad() {
super.viewDidLoad()
Report.isHidden = true
}
//Scoring Buttons
#IBAction func BadContact(_ sender: Any) {
sum -= 1
BadContact1.setTitle("OK", for: .normal)
badcontactpct += 1
}
#IBAction func Left(_ sender: Any) {
sum -= 0.5
help += 1
Left1.setTitle(String(help), for: .normal)
if help == 1 {
leftpct += 1
totalpct += 1}
}
#IBAction func Right(_ sender: Any) {
sum -= 0.5
help += 1
Right1.setTitle(String(help), for: .normal)
if help == 1 {
rightpct += 1
totalpct += 1}
}
#IBAction func Center(_ sender: Any) {
Center1.setTitle("0", for: .normal)
centerpct += 1
totalpct += 1
}
Thanks for taking the time to write such a thorough question. If you want, you can upload your images to some other site (e.g. imgur) and then use the direct links here.
As matt mentions in the comments, that as! might be the reason you're seeing this crash. You can easily prove it if you replace your code to something like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let finalCompleteTest = segue.destination as? FinalCompleteTest {
finalCompleteTest.score = String(sum)
finalCompleteTest.Leftpct = Int(leftpct)
finalCompleteTest.Rightpct = Int(rightpct)
finalCompleteTest.Centerpct = Int(centerpct)
finalCompleteTest.Totalpct = Int(totalpct)
finalCompleteTest.BadContactpct = Int(badcontactpct)
} else {
print("oh no, segue.destination is not a FinalCompleteTest")
}
}
If you can see that message in your console, probably you haven't connected something correctly on the storyboard.
If not, try to give us some more information.
If you don't understand the "dangers" of using ! in your code, please try to understand Optionals first and don't hesitate to ask if you need more help.
Good luck!
I am a beginner student in Swift 3 and I am currently studying CoreData. I am trying to do an App where I have a first controller that is a list view (tableviewcontroller) where I can see some students. Inside each cell, I have an image (UIImage) and 4 labels (Strings: name, preceptor, note and date) who fetch data from an Array that keeps the information from the entity "AlunosLista", who has one attribute for each item (image is binary data). I can add these information through another view controller (AddDataVC.swift) and list them perfectly. The app until here is fine. What i cannot do, and i have been trying a lot of things, many things, is to send the data from the row selected (clicked) to another viewcontroller for the detailed view (DetailsVC.swift). When i was using a simple Array, without CoreData, worked fine. But now i cannot do it. Now parts of the code:
File (1): TableviewController
class TabelaListagem: UITableViewController {....
import CoreData
import UIKit
var alunos: [NSManagedObject?] = []
var gerenciadorDeDados: NSManagedObjectContext? = nil
override func viewDidLoad() {
super.viewDidLoad()
//CORE DATA
let AppleObject = UIApplication.shared.delegate as! AppDelegate
gerenciadorDeDados = AppleObject.persistentContainer.viewContext
LoadFetch()
}
(.......)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let path = alunos[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "celulaReuso", for: indexPath) as! ListagemCelulas
cell.lblAluno.text = path?.value(forKey: "nome") as? String
cell.lblPreceptor.text = path?.value(forKey: "preceptor") as? String
cell.lblData.text = path?.value(forKey: "dataHoje") as? String
cell.lblNotaAluno.text = path?.value(forKey: "nota") as? String
let caminhodaImagem = path?.value(forKey: "fotoAluno")
cell.imgAluno.image = UIImage(data: (caminhodaImagem as? NSData) as! Data)
return cell
}
Here should place the prepare(for segue), that I have tried many ways. This was the last one, who didn't worked too. "Build succeeded", but crashed.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueAluno" {
if let pathC = tableView.indexPathForSelectedRow{
let VCDestino = segue.destination as? DescriAluno
let objeto = FecthResultado?.object(at: pathC)
VCDestino?.alunoD = objeto as! NSManagedObject?
}
}
}
File (2) DetailViewController
import UIKit
import CoreData
class DescriAluno: UIViewController {
#IBOutlet weak var imgFotoAluno: UIImageView!
#IBOutlet weak var nomeAluno: UILabel
#IBOutlet weak var txtPreceptor: UILabel!
#IBOutlet weak var txtNotaAluno: UILabel!
#IBOutlet weak var txtDataHoje: UILabel!
var gerenciadorDeDados: NSManagedObjectContext!
var alunoD: NSManagedObject?
override func viewDidLoad() {
super.viewDidLoad()
//CORE DATA
let AppleObject = UIApplication.shared.delegate as! AppDelegate
gerenciadorDeDados = AppleObject.persistentContainer.viewContext
imgFotoAluno.image = alunoD?.value(forKey: "fotoAluno")
nomeAluno.text = alunoD?.value(forKey: "nome")
txtPreceptor.text = alunoD?.value(forKey: "preceptor")
txtNotaAluno.text = alunoD?.value(forKey: "nota")
txtDataHoje.text = alunoD?.value(forKey: "dataHoje")
}
Error message after crash:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'executeFetchRequest:error:
is not a valid NSFetchRequest.'
I really don't know how to proceed. I have tried so many things, some of them told about NSFetchedResults, but i could write or understand them. If any one could help here, I appreciate. Thank you.
This is the Fetch request (func):
Ok. This is my fetch request:
func LoadFecth() {
let ordenacaoAZ = NSSortDescriptor(key: "nome", ascending: true)
let ordenacaoAZPrecep = NSSortDescriptor(key: "preceptor", ascending: true)
let recupoerardados = NSFetchRequest<NSFetchRequestResult>(entityName: "AlunosLista")
recupoerardados.sortDescriptors = [ordenacaoAZPrecep, ordenacaoAZ]
do{
let recupera = try gerenciadorDeDados?.fetch(recupoerardados)
self.alunos = recupera as! [NSManagedObject]
self.tableView.reloadData()
}catch let erro as NSError{
print("Erro ao carregar: \(erro.description)")
}
}
Your core data life will get easier in every way if you use NSManagedObject subclasses for your entities. This allows you to use convenience accessors to get typed fetch requests and to skip all this value(forKey: stuff and use properties.
The error is that your fetch request is not valid.
You may have mistyped the entity name ("AlunosLista").
It may be getting upset because the type of the fetch request, without a specific NSManagedObject subclass, is actually NSFetchRequest<NSManagedObject>.
The best thing to do is enter a subclass for the entity in the model editor, generate the subclass files, then use this:
let recupoerardados: NSFetchRequest<AlunosLista> = AlunosLista.fetchRequest()
I had an a problem with this code, this identifier didn't run with me
import UIKit
class RestaurantDetailViewController: UIViewController {
#IBOutlet var restaurantImageView: UIImageView!
var restaurantImage = ""
override func viewDidLoad () {
super.viewDidLoad ()
restaurantImageView.image = UIImage(named: restaurantImage)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showRestaurantDetail" {
if let indexPath = tableView.indexPathSelectedRow {
let destinationController = segue.destinationViewController as! RestaurantDetailViewController
destinationController.restaurantImage = restaurantImageView[indexPath.row]
}
}
}
}
The error is : Use of unresolved identifier 'tableView'
Looking for a solution.
Thanks,
The prepareForSegue method is obviously in the wrong class (copy and paste error?).
It belongs to the Master controller.
Do you have a tableview in your VIEWCONTROLLER ? If yes then create a IBOOutlet between your class and viewcontroller
you can see this question or this this for reference.
This behavior is called "scope" and is crucial to any programming language. Variables declared inside a method are neither visible outside it nor do they persist when that method has finished running. and in your case for creating tableView variable you have to create an IBOoutlet or you can define it programmatically too you can get more detail from here
and here
On my app there are several views where you input certain values (settings) and so these settings get sent back to the master view using delegates. I already had this setup with 2 other views, and just copied and pasted the required code and changed the variables, however this brought up the error; "Type 'MasterViewController' does not conform to protocol 'CropPerformanceControllerDelegate'"The Code is below:
// MasterViewController
class MasterViewController: UIViewController, SettingsControllerDelegate, RainfallDataControllerDelegate, CropPerformanceControllerDelegate {
// Other variables/functions...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "MasterToSettings" {
let vc = segue.destinationViewController as! Settings
vc.delegate = self
}
if segue.identifier == "MasterToRainfallData" {
let vc = segue.destinationViewController as! RainfallData
vc.delegate = self
}
if segue.identifier == "MasterToCropPerformance" {
let vc = segue.destinationViewController as! CropPerformance
vc.delegate = self
}
}
func cropPerformanceSettingsSaved(controller: CropPerformance, irrigationResponseFactor: Double, wdImpactFactor: Double, potentialYield: Double) {
controller.navigationController?.popViewControllerAnimated(true)
irrigationResponseFactorM = irrigationResponseFactor
wdImpactFactorM = wdImpactFactor
potentialYieldM = potentialYield
}
}
// New View
// CropPerformanceView
protocol CropPerformanceControllerDelegate {
func cropPerformanceSettingsSaved(controller: CropPerformance, irrigationResponseFactor: Double, wdImpactFactor: Double, potentialYield: Double)
}
class CropPerformance: UIViewController {
var delegate: CropPerformanceControllerDelegate? = nil
// Other functions and Variables
#IBAction func updateCropSettings(sender: AnyObject) {
// Other stuff
if (delegate != nil) {
delegate!.cropPerformanceSettingsSaved(self, irrigationResponseFactor: irrigationResponseFactor!, wdImpactFactor: wdImpactFactor!, potentialYield: potentialYield!)
}
}
}
So this exact same code is used for the settings and rainfallData views and there are no issues, however now on the master view the 'CropPerformanceControllerDelegate' does not seem to be recognised and any uses of the class "CropPerformance" cause the error; "Use of undeclared type 'CropPerformance'". I hope this is enough information, all code to do with the delegate is posted, all other unnecessary variable declarations and functions I left out.
I had looked for other answers and they all said that you need to implement all required methods if you want to conform to the protocols. What exactly does that mean? All of my functions are inside my class and the code works perfectly when I remove the parts regarding the delegate.
Thanks in advance.
I was not able to solve the problem as there was no mistake in the code, I simply started from scratch, made a new document, copy and pasted the same code, changed the name to CropPerformance2 and it worked. (It works with any other name except for CropPerformance). This is very strange and must have something to do with that the initial class "CropPerformace" that I had deleted as not completely wiped from the systems memory, at least that is my assumption. In conclusion there is no answer, I simply recreated everything and it worked.
Sorry in advance that I can’t explain myself very well. I’m really new to programming and the topic of delegation still eludes me. I had some great help with this once before, but now I am trying to use a delegate in a different situation and I can’t get it right. I pieced together a bit of code that doesn’t work, and no matter how much I search I can’t find a way to fix it.
I have a view controller (MainController) with and embedded view controller (EmbeddedController) in a container view. I am trying to have a button in the embedded controller manipulate the container view (containerView).
EmbeddedController:
protocol ControllerDelegate {
func hideContainerView()
}
class EmbeddedController: UIViewController {
var delegate: VControllerDelegate?
#IBAction func button(sender: AnyObject) {
delegate?.hideContainerView()
}
}
MainController:
class MainController: UIViewController, ControllerDelegate {
#IBOutlet var containerView: UIView!
func hideContainerView() {
containerView.hidden = true
}
override func viewDidLoad() {
super.viewDidLoad()
var vc = EmbeddedController()
vc.delegate = self
}
}
Does anyone have any idea what I am doing wrong? And why this isn’t working?
What I ended up doing is adding this to the MainController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "mySegue") {
let vc = segue.destinationViewController as! EmbeddedController
vc.delegate = self
}
}
In storyboard I selected the segue from the MainController to the EmbeddedController, and set the identifier to "mySegue".
Without the code above the delegate kept returning nil. I didn't look into this solution at first as I thought segues were only for transitioning between view controllers, and in my mind I didn't see the embedded controller as a transition. Maybe someone more knowledgable than me (which is practically anyone on here at this point) can explain how this is all fitting together.
In any case, this is how I solved my issue and hopefully someone else can benefit from this as well :)
First of all, to avoid strong reference cycles:
protocol ControllerDelegate: class {
func hideContainerView()
}
class EmbeddedController: UIViewController {
weak var delegate: ControllerDelegate?
And you haven't added your newly instantiated VC view to container view, and not added it as a child VC:
let vc = EmbeddedController()
vc.delegate = self
containerView.addSubview(vc.view)
self.addChildViewController(vc)
vc.didMoveToParentViewController(self)