passing a core data object in pieces instead of one object - swift

normally
in segues i pass coredata object instances in their entirety e.g.
if segue.identifier == "postCall" {
let vc: PostCallViewController = segue.destinationViewController as PostCallViewController
let indexPath = tableView.indexPathForSelectedRow()
let cell = fetchedResultsController.objectAtIndexPath(indexPath!) as Contacts
vc.contact = cell
}
but
this time i want to load vc.contact with data from different sources
#IBOutlet weak var pic: UIImageView!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "numberSelected" {
let vc: EditViewController = segue.destinationViewController as EditViewController
vc.contact.status = "newCall"
vc.contact.created = NSDate()
vc.contact.phone = selectedNumber
vc.contact.phoneType = selectedType
vc.contact.name = nameLabel.text!
vc.contact.photo = UIImageJPEGRepresentation(pic.image,1)
vc.contact.memo = ""
}
}
editcontroller
class EditViewController: UIViewController {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var phoneLabel: UILabel!
#IBOutlet weak var numField: UITextField!
#IBOutlet weak var userPic: UIImageView!
#IBOutlet weak var memoArea: UITextView!
var img:NSData!
var contact: Contacts!
let appDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
nameField.text = contact?.name
phoneLabel.text = contact?.phoneType
numField.text = contact?.phone
memoArea.text = contact?.memo
img = contact?.photo
userPic.image = UIImage(data: img)
}
the error
i keep on getting optional is nil errors on
vc.contact.phone = selectedNumber
and the rest of lines in that block even though none of them are optional variables.
question
is this type of passing data with a coredata object broken into its attribute allowed or not?
i think i could just pass variables individually without using core data, but there is another segue that leads to the same controller which uses the coredata contact entity and i would like to keep it consistent

Related

Sigbart error during prepare for segue/unwind segue

I have an app where it is a marketplace and when you click on a product, it opens a detail view controller passing the data to display on the DetailVC. Additionally, inside the DetailVC, if you click on a button to claim the product, it segues to another VC to finalize the transaction.
In the DetailVC, there is a back button which is an unwind segue back to the main marketplace VC. Inside the TransactionVC, there is a cancel button which takes you back to the DetailVC.
When I am clicking the backButton in the DetailVC to take me back to the main market VC but I am getting a SIGBART Error and this :
020-07-15 09:05:23.707490-0500 evolutionatx[707:141952] Could not cast value of type 'evolutionatx.MarketplaceViewController' (0x1032c7868) to 'evolutionatx.PopUpPurchaseViewController' (0x1032c7ba8).
Here is the code for the DetailVC
import UIKit
import iCarousel
import CoreData
class MarketDetailViewController: UIViewController, UIScrollViewDelegate, iCarouselDelegate, iCarouselDataSource {
var productImageArray = [UIImage]()
var productVideo = String()
var pointsToPurchase = String()
var productName = String()
var productDescription = String()
var companyLogo = UIImage()
var companyWebsite = String()
var additionalProductImage = [UIImage]()
var companyName = String()
var promoCODE = String()
var buyLink = String()
var slides:[Slide] = [];
//IB
#IBOutlet weak var productNameLabel: UILabel!
#IBOutlet weak var productPriceLabel: UILabel!
#IBOutlet weak var productDescLabel: UILabel!
#IBOutlet weak var claimButton: UIButton!
#IBOutlet weak var imgScrollView: UIScrollView!
#IBOutlet weak var websiteButton: UIButton!
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var logoDisplay: UIImageView!
#IBOutlet weak var carouselView: iCarousel!
#IBOutlet weak var otherProductslabe: UILabel!
var carouselImages = [UIImage]()
var evoCoin = Int()
override func awakeFromNib() {
super.awakeFromNib()
carouselImages = productImageArray
}
override func viewDidLoad() {
super.viewDidLoad()
valueSetter()
imgScrollView.delegate = self
slides = createSlides()
setupSlideScrollView(slides: slides)
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubviewToFront(pageControl)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
pageControl.currentPage = Int(pageIndex)
let maximumHorizontalOffset: CGFloat = scrollView.contentSize.width - scrollView.frame.width
let currentHorizontalOffset: CGFloat = scrollView.contentOffset.x
// vertical
let maximumVerticalOffset: CGFloat = scrollView.contentSize.height - scrollView.frame.height
let currentVerticalOffset: CGFloat = scrollView.contentOffset.y
let percentageHorizontalOffset: CGFloat = currentHorizontalOffset / maximumHorizontalOffset
let percentageVerticalOffset: CGFloat = currentVerticalOffset / maximumVerticalOffset
}
#IBAction func claimProduct(_ sender: Any) {
print("tap rec")
claimProductandPurchase()
}
func claimProductandPurchase(){
evoCOiner()
if(evoCoin >= Int(pointsToPurchase)!){
print("Transaction Successful")
performSegue(withIdentifier: "proceedQuestion", sender: self)
}
else{
showToast(controller: self, message: "Insufficient EvoCoins", seconds: 0.5)
}
}
func showToast(controller: UIViewController, message : String, seconds: Double) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
alert.view.backgroundColor = UIColor.black
alert.view.alpha = 0.6
alert.view.layer.cornerRadius = 15
controller.present(alert, animated: true)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds) {
alert.dismiss(animated: true)
}
}
func evoCoiner(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "EvoCoins")
request.returnsObjectsAsFaults = false
do{
let result = try context.fetch(request)
for data in result as! [NSManagedObject]
{
evoCoin = data.value(forKey: "evoCoins") as! Int
}
}catch{
print("Failed")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let detailController = segue.destination as! PopUpPurchaseViewController
detailController.ppromo = promoCODE
detailController.link = buyLink
detailController.coinToPurchase = Int(pointsToPurchase)!
}
//This is the unwind used by the transaction back button
#IBAction func unwindToItem(segue: UIStoryboardSegue) {
}
}
Here is the code in the transaction VC
import UIKit
import AMTabView
import CoreData
class MarketplaceViewController: UIViewController, TabItem {
#IBOutlet weak var sView: UIView!
#IBOutlet weak var evoCoinLabe: UILabel!
//For the sake of simplicity I only kept the Unwind functions
//MARK: - UNWIND FUNCTIONS
#IBAction func unwindToMainMarketView(segue: UIStoryboardSegue) {
}
}
How can I fix this error?
Please advise if my question was not clear or properly phrased (if so, sorry I am pretty new to all of this)
As #matt already said in his comment and the error clearly states, you cannot cast a MarketplaceViewController to a PopUpPurchaseViewController.
Furthermore instead of doing a forced cast, always look for a safe one like below. Doing so will prevent crashes.
if let detailController = segue.destination as? PopUpPurchaseViewController {
...
}
else {
// log failed to cast
}

How to pass data between view controllers - not working

Im trying to pass an player score from VC1 to a view that displays the current scores of all players(4). the display view is on a separate view controller than where the player(s) score is defined and stored.
I have done the prepare(segue) and im able to pass other variables to the display VC(ThirdViewController). but when I try to assign the player score to the uilabel.text It tells me that I have unwrapped a nil value
I have even tried to just set the label text to a static string and still get the nil error.
class ViewController: UIViewController {
var name = String()
var player1Score = 1
var player2Score = 2
var player3Score = 3
var player4Score = 4
//MARK: ********* IBOutlets **********
#IBOutlet weak var playerSegmentOutlet: UISegmentedControl!
#IBOutlet weak var diceSegmentOutlet: UISegmentedControl!
#IBOutlet weak var targetScoreSliderOutlet: UISlider!
#IBOutlet weak var matchTargetSwitchOutlet: UISwitch!
#IBOutlet weak var targetScoreLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? SecondViewController {
VC.player1CurrentScore = player1Score
VC.player2CurrentScore = player2Score
VC.player3CurrentScore = player3Score
VC.player4CurrentScore = player4Score
}
}
Second view Controller
class SecondViewController: UIViewController {
#IBOutlet weak var CurrentRoundScoreLabel: UILabel!
#IBOutlet weak var CurrentPlayerScoreLabel: UILabel!
var player1CurrentScore = 1
var player2CurrentScore = 1
var player3CurrentScore = 1
var player4CurrentScore = 1
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {
VC.player1ScoreLabel.text = String(player1CurrentScore)
VC.player2ScoreLabel.text = String(player2CurrentScore)
VC.player3ScoreLabel.text = String(player3CurrentScore)
VC.player4ScoreLabel.text = String(player4CurrentScore)
}
}
Third View Controller
class ThirdViewController: UIViewController {
#IBOutlet var player1ScoreLabel: UILabel!
#IBOutlet var player2ScoreLabel: UILabel!
#IBOutlet var player3ScoreLabel: UILabel!
#IBOutlet var player4ScoreLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
no matter what I try to do with with UILabel.text it shows up as nil
I'm totally frustrated and I'm sure I am missing something simple because of my frustration, please someone help me.
This is an inefficient way to do this as you are passing data through 3 different objects. However, going on with this methodology, the problem is the label's are not created yet inside
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {
VC.player1ScoreLabel.text = String(player1CurrentScore)
VC.player2ScoreLabel.text = String(player2CurrentScore)
VC.player3ScoreLabel.text = String(player3CurrentScore)
VC.player4ScoreLabel.text = String(player4CurrentScore)
}
}
See, the label isn't created yet. So, you are setting text on aUILabel that isn't initialized. Therefore, you need to create variables for the labels inside ThirdViewController.
Third View Controller
class ThirdViewController: UIViewController {
#IBOutlet var player1ScoreLabel: UILabel!
#IBOutlet var player2ScoreLabel: UILabel!
#IBOutlet var player3ScoreLabel: UILabel!
#IBOutlet var player4ScoreLabel: UILabel!
var score0:Int!
var score1:Int!
var score2:Int!
var score3:Int!
override func viewDidLoad() {
super.viewDidLoad()
self.player1ScoreLabel.text = String(score0)
self.player2ScoreLabel.text = String(score1)
self.player3ScoreLabel.text = String(score2)
self.player4ScoreLabel.text = String(score3)
}
}
and change the segue in SecondViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {
VC.score0 = player1CurrentScore
VC.score1 = player2CurrentScore
VC.score2 = player3CurrentScore
VC.score3 = player4CurrentScore
}
}
Another Way:
Let's create a singleton called Game. Within this (assuming you only have 4 players) we can create 4 players that will never change. This allows us to create instances of players in one location and call upon them as necessary.
NOTE: A singleton can be misused EASILY.
https://cocoacasts.com/what-is-a-singleton-and-how-to-create-one-in-swift
https://cocoacasts.com/are-singletons-bad/
class Game {
static var score0:Int = 0
static var score1:Int = 0
static var score2:Int = 0
static var score2:Int = 0
}
Then, anywhere in your code you can access Game.score0, Game.score1.
CAUTION:
I would caution you to very carefully use singletons. You don't want everything with public access. You need to determine if this is good for you or not. Cheers.

pass data from tableviewcontroller to another tableviewcontroller in swift

I have a form I am creating
this form gets filled with textfields the user inputs. After answering all the questions a button pops up to save.
I am having a problem making this tableviewcontroller to pass the data to a new tableviewcontroller. I'm stuck and not sure how to go about this.
import UIKit
class TableViewController: UITableViewController, UITextFieldDelegate {
#IBOutlet weak var saveBtn: UIButton!
#IBOutlet var firstNameField: UITextField!
#IBOutlet var middleNameField: UITextField!
#IBOutlet weak var lastNameField: UITextField!
#IBOutlet weak var addressField: UITextField!
#IBOutlet weak var aptNumField: UITextField!
#IBOutlet weak var cityField: UITextField!
#IBOutlet weak var stateField: UITextField!
#IBOutlet weak var zipField: UITextField!
#IBOutlet weak var phoneOneField: UITextField!
#IBOutlet weak var phoneTwoField: UITextField!
#IBOutlet weak var allergiesField: UITextField!
#IBOutlet weak var DobField: UILabel!
#IBOutlet weak var sexField: UILabel!
#IBOutlet weak var hospitalField: UITextField!
#IBOutlet weak var doctorField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//Notifications to push datepicker
NotificationCenter.default.addObserver(forName: .saveDateTime, object: nil, queue: OperationQueue.main) { (notification) in
let dateVc = notification.object as! DatePopupViewController
self.DobField.text = dateVc.formattedDate
}
//Notifications to push genderpicker
NotificationCenter.default.addObserver(forName: .saveGender, object: nil, queue: OperationQueue.main) { (notification) in
let genderVc = notification.object as! GenderPopupViewController
self.sexField.text = genderVc.selectedGender
}
updateWidthsForLabels(labels: labels)
}
//Save Button Function
func textFieldDidChange(_ textField: UITextField) {
if textField == firstNameField || textField == lastNameField || textField == middleNameField || textField == addressField || textField == lastNameField || textField == cityField || textField == cityField || textField == stateField || textField == zipField || textField == phoneOneField || textField == phoneTwoField || textField == allergiesField {
saveBtn.isHidden = true
} else {
saveBtn.isHidden = false
}
}
#IBAction func saveBtnPressed(_ sender: Any) {
performSegue(withIdentifier: "saveFirstPageSegue", sender: self)
}
}
what about starting creating a model:
Form.swift
struct Form {
var firstname: String?
var middlename: String?
....
var doctor: String?
init(firstname: String, middlename: String, ..., doctor: String) {
self.firstname = firstname
self.middlename = middlename
...
self.doctor = doctor
}
}
now you can create this form instance when saving and pushing the data to the new VC:
yourCurrentForm.swift
#IBAction func saveBtnPressed(_ sender: Any) {
let formData = Form(firstname: firstNameField.text, middlename: middleNameField.text, ..., doctor: doctorField.text)
let newVC = myNewViewController()
newVC.form = formData
self.navigationController?.pushViewController(newVC, animated: true)
}
NewViewController.swift
class myNewViewController: UIViewController {
var form: Form?
.....
}
UPDATE:
Here is the repo: https://github.com/FlorianLdt/LFEasyDelegate
If you have some question just ask me
Hope it helps.
First Option - Structs - Preferred
Make use of Structs :
struct Manager
{
static var value : String = ""
}
Noe Update value of that function by just calling
Manager.value = "newValue"
Access that value anywhere Assign it to other Variables
let newStr : String = Manager.value
Second Option - AppDelegate - Not ideal
Create new object in AppDelegate
Now create a new object to access appDelegate
let appDel = UIApplication.shared.delegate as! AppDelegate
Access Value and update as below
appDel.Frequency = 1.0
Third Option - NSObjectClass
Create a new NSObject class as below
//Instance created when NSObject class is first time loaded in memory stack
static let shared = wrapperClass()
//Create a value to access Globally in Object class
var newValueInClass : String = ""
Now time to access that created Object
wrapperClass.shared.newValueInClass = "iosGeek"
Now Anywhere write this Line
print(wrapperClass.shared.newValueInClass)
Console Output
Better to use struct classes to manage data globally

Swift : Only transfers certain variable when using prepareforsegue function

I am using the prepareforsegue function to transfer data between view controllers. I am attempting to transfer the data held by 5 variables yet only 4 transfer. I am wondering if it is a formatting issue considering the 4 that transfer correctly have different variable types to the other. Any help would be greatly appreciated. Thank you for your time.
Sender View Controller
import Foundation
import UIKit
class CratesViewController : UIViewController {
var unlockedAK47 = "false"
var unlockedDesertEagle = "false"
var unlockedGlock17 = "false"
var unlockedGlock18 = "false"
var coinsAmount = 0
let screenSize: CGRect = UIScreen.main.bounds
#IBOutlet weak var coins: UILabel!
#IBOutlet weak var cratesImageView: UIImageView!
#IBOutlet weak var unlockView: UIView!
#IBOutlet weak var backButtonOutlet: UIButton!
#IBOutlet weak var gunImageView: UIImageView!
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
coins.text = String(coinsAmount)
gunImageView.isHidden = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "cratesToMainSegue" {
let destViewController = segue.destination as! ViewController
destViewController.unlockedAK47 = unlockedAK47
destViewController.unlockedDesertEagle = unlockedDesertEagle
destViewController.unlockedGlock17 = unlockedGlock17
destViewController.unlockedGlock18 = unlockedGlock18
destViewController.coinsAmount = coinsAmount
}
}
}
Destination View Controller
import UIKit
class ViewController: UIViewController {
var unlockedAK47 = "false"
var unlockedDesertEagle = "false"
var unlockedGlock17 = "true"
var unlockedGlock18 = "false"
struct defaultsKeys {
static let keyOne = ""
}
var coinsAmount = 100
#IBOutlet weak var coinsAmountLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Getting
let defaults = UserDefaults.standard
if let stringOne = defaults.string(forKey: defaultsKeys.keyOne) {
coinsAmount = Int(stringOne)!
}
coinsAmountLabel.text = String(coinsAmount)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Note that the segue identifiers have been set in the interface builder
if segue.identifier == "cratesSegue" {
let destViewController = segue.destination as! CratesViewController
destViewController.coinsAmount = coinsAmount
destViewController.unlockedAK47 = unlockedAK47
destViewController.unlockedDesertEagle = unlockedDesertEagle
destViewController.unlockedGlock17 = unlockedGlock17
destViewController.unlockedGlock18 = unlockedGlock18
} else if segue.identifier == "collectionSegue" {
let destViewController = segue.destination as! CollectionViewController
destViewController.coinsAmount = coinsAmount
destViewController.unlockedAK47 = unlockedAK47
destViewController.unlockedDesertEagle = unlockedDesertEagle
destViewController.unlockedGlock17 = unlockedGlock17
destViewController.unlockedGlock18 = unlockedGlock18
}
}
#IBAction func saveButton(_ sender: Any) {
coinsAmount = coinsAmount + 10
coinsAmountLabel.text = String(coinsAmount)
// Saving
let defaults = UserDefaults.standard
defaults.set(String(coinsAmount), forKey: defaultsKeys.keyOne)
}
}
Please let me know if any more information is needed. :)
This is not because coinsAmount is a different type. This is because coinsAmount's value is overwritten in viewDidLoad:
let defaults = UserDefaults.standard
if let stringOne = defaults.string(forKey: defaultsKeys.keyOne) {
coinsAmount = Int(stringOne)! <<<< HERE!
}
I think what you want to do is to get the coins amount only if it is not passed to ViewController.
You should declare coinsAmount as an optional:
var coinsAmount: Int!
And check whether it is nil before overwriting it, so that you don't accidentally overwrite the passed value.
if coinsAmount == nil {
let defaults = UserDefaults.standard
if let stringOne = defaults.string(forKey: defaultsKeys.keyOne) {
coinsAmount = Int(stringOne)!
}
}

Transfer TextField data between ViewControllers

I have ViewController, which has a textfield and a button. The user enters his name into the textfield and hits the DONE button. When he hits the button, he is segued to GifteeDetails, which is a different view controller. There is a label in that viewController that is supposed to display his name. But, his name doesn't show up. I don't receive an error.
Here's ViewController:
#IBOutlet weak var textGifteeName: UITextField!
#IBAction func toDetails(_ sender: Any) {
performSegue(withIdentifier: "toDetails", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destDetails: GifteeDetails = segue.destination as! GifteeDetails
destDetails.nametext = textGifteeName.text!
destDetails.agetext = "\(Int(age)! - 2000 + 17)"
destDetails.locationstext = labelGifteeLocationsPreview.text!
destDetails.intereststext = labelGifteeInterestsPreview.text!
}
Here's GifteeDetails:
var nametext = String()
var agetext = String()
var locationstext = String()
var intereststext = String()
#IBOutlet weak var labelGifteeName: UILabel!
#IBOutlet weak var labelGifteeAge: UILabel!
#IBOutlet weak var labelGifteeLocations: UILabel!
#IBOutlet weak var labelGifteeInterests: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
nametext = labelGifteeName.text!
agetext = labelGifteeAge.text!
locationstext = labelGifteeLocations.text!
intereststext = labelGifteeInterests.text!
}
Sorry about all the !. Swift gives me an error unless I have them.
You are updating the strings nametext and others, which are not connected to your labels.
You need to replace this piece of code:
destDetails.nametext = textGifteeName.text!
destDetails.agetext = "\(Int(age)! - 2000 + 17)"
destDetails.locationstext = labelGifteeLocationsPreview.text!
destDetails.intereststext = labelGifteeInterestsPreview.text!
With:
destDetails.labelGifteeName.text = textGifteeName.text!
destDetails.labelGifteeAge.text = "\(Int(age)! - 2000 + 17)"
destDetails.labelGifteeLocations.text = labelGifteeLocationsPreview.text!
destDetails. labelGifteeInterests.text = labelGifteeInterestsPreview.text!
nametext is a String object, and it is different from labelGifteeName.text which is the String of the label you want to update.
If you use segue then pls remove it, and then create action method of that button in the FirstviewController and use pushViewController to move on the SecondviewController.
Swift 3
Example:
#IBAction func toDetails(_ sender: Any)
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "SecondView") as! SecondView
UserDefaults.standard.set(self. textGifteeName.text, forKey: "savedStringKey")
UserDefaults.standard.synchronize()
self.navigationController?.pushViewController(vc,
animated: true)
}
In SecondView:
override func viewDidLoad() {
super.viewDidLoad()
self.labelGifteeName.text = UserDefaults.standard.string(forKey: "savedStringKey")!
}
Please Update your func viewDidLoad() method in GifteeDetailsVC with labelGifteeName.text! = nametext , labelGifteeAge.text!= agetext instead of your code because you have already assigned the value in to strings i.e, nametext and agetext in ViewController, you need to display string value in label