fatal error.. when accessing variables from another class - ios

I have been struggling to access label from UITableViewCell in a UITableViewController..
The code for the class reboundShotsCount looks like this:
This class is defined inside the ShotDetail Class which is subclass of UITableViewController.
public class reboundShotsCount : UITableViewCell {
#IBOutlet public var reboundCountLabel: UILabel!
}
The code in ShotDetail
var shots : [Shot] = [Shot]()
var shot : Shot!
var comments : [Comment] = [Comment]()
var previousImageManager : ImageManager!
override func viewDidLoad() {
super.viewDidLoad()
// Enable GIF decompression
self.previousImageManager = ImageManager.shared
let decoder = ImageDecoderComposition(decoders: [AnimatedImageDecoder(), ImageDecoder()])
let loader = ImageLoader(configuration: ImageLoaderConfiguration(dataLoader: ImageDataLoader(), decoder: decoder), delegate: AnimatedImageLoaderDelegate())
let cache = AnimatedImageMemoryCache()
ImageManager.shared = ImageManager(configuration: ImageManagerConfiguration(loader: loader, cache: cache))
title = shot.title
let reboundShotCell = reboundShotsCount()
// the countLabel returns nil
let countLabel = reboundShotCell.reboundCountLabel
// The error comes here.
countLabel.text = "\(shot.reboundCount)"
let api = DribbleObjectHandler()
api.loadComments(shot.commentsUrl, completion: didLoadComments)
api.loadShots(shot.reboundUrl, completion: didLoadReboundShots)
}
i am aware that the code creates a new instance of reboundShotsCount in viewDidLoad..
And if i define shot variable inside the reboundShotsCount class. it return's nil.
I am stuck and dont know what to do?

You need to set text in cellForRowAtIndexPath dataSource method of tableview....What you did is in viewDidLoad and at that point tableview has not loaded yet and neither tableviewcell –

Related

Swift NavigationBar Press "Back" to get values, why?

I am using some values to perform some calculations. For testing purposes I show in Label1 a value as string, since it is stored as a string and in Label2 I show a casted value as a Double since I need them at the end as doubles for my calculations.
The weird thing is, that when I access the ViewController the first time it doesn't show any values. But if I go back and klick on it again using the navigation controller it actually works. But I need the values right away cause my original intention is as I said, not showing some labels but rather making some calculations with it.
I made a little gif to show you what the problem is but I have problem with adding photos. Basically what happens is, that I click on the ViewController with the labels and nothing is showed. I go back and press again and the values will be showed in the labels.
Why is that and how can it be showed right away/ used for calculations right away
Thanks for the help. :)
class AHPfinalPreferencesViewController: UIViewController {
var ahpPrios = [AHPPriorityStruct]()
let decoder = JSONDecoder()
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
let ajkpXc = globaLajkpXc
let ajkpXijr = globaLajkpXijr
let valueA = globaLajkpXc
let valueB = Double(globaLajkpXijr)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UserService.ahpPref(for: User.current) { (ahpPrios) in
self.ahpPrios = ahpPrios
print("This is our AHP PRIOS", ahpPrios)
for ahpPrio in ahpPrios {
print(ahpPrio)
}
print("this is the global ajk. ", self.ajkpXc)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Mark: - Get Data
label1.text = valueA
label2.text = "\(String(describing: valueB))"
// MARK: - Set Values for calculation
// setValues()
// ahpCalculation()
}
}
Could it be because of the globalVariables? I know that it is not the right way to do it but for my purposes its absolutely "okay"
import Foundation
import FirebaseAuth.FIRUser
import FirebaseDatabase
import FirebaseUI
import FirebaseAuth
import CodableFirebase
var globaLajkpXc: String = String()
var globaLajkpXijr: String = String()
var globaLajkpXqpa: String = String()
struct UserService {
static func ahpPref(for user: User, completion: #escaping ([AHPPriorityStruct]) -> Void) {
let ref = Database.database().reference().child("AHPRatings").child(user.uid)
ref.observe(DataEventType.value, with: { snapshot in
guard let value = snapshot.value else { return }
do {
let ahpPrios = try FirebaseDecoder().decode(AHPPriorityStruct.self, from: value)
print(ahpPrios)
// MARK: - lets store the values in the actual constants :)
let ajkpXc = ahpPrios.ajkpXc
let ajkpXijr = ahpPrios.ajkpXijr
let ajkpXqpa = ahpPrios.ajkpXqpa
globaLajkpXc = ajkpXc ?? "no Value"
globaLajkpXijr = ajkpXijr ?? "no Value"
globaLajkpXqpa = ajkpXqpa ?? "no Value"
} catch let error {
print(error)
}
})
}
}
[1]: https://i.stack.imgur.com/VKxaE.png
You are calling UserService's ahpPref in your controller's viewWillAppear. BUT you are attempting to put your valueA (globaLajkpXc's value) to your label in your controller's viewDidLoad.
So what does that mean? Do you know which of these two controller's life cycle method gets called and when they do get called?
To solve your problem, have your label assigning value code
label1.text = globaLajkpXc
move in the completion block of your ahpPref (in the viewWillAppear).
Here's the Apple's documentation about the UIViewController's lifecycle: https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/WorkWithViewControllers.html
Also, below this line: globaLajkpXqpa = ajkpXqpa ?? "no Value"
add your completion call, like:
completion([ahpPrios]).
This should make my answer above work.

Swift: Change UITextField Value from inside NSItemProvider

I've been battling this one all day, as you can see I'm new to Swift.
I'm trying to update a UITextField in a iOS Action Extension. Correct value logs below, and outside of the loadItem() the correct value can be set.
Weak variable in the outlet? Some sort of closure thing? Function fires asynchronously and I'm not allowed to update the UI this way once it finishes?
Thank you in advance!
class ActionViewController: UIViewController {
//outlets
#IBOutlet weak var bookmarkTitle: UITextField!
//vars
var pageTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
if let inputItem = extensionContext!.inputItems.first as? NSExtensionItem {
if let itemProvider = inputItem.attachments?.first as? NSItemProvider {
itemProvider.loadItem(forTypeIdentifier: kUTTypePropertyList as String) { [unowned self] (dict, error) in
// do stuff!
let itemDictionary = dict as! NSDictionary
let javaScriptValues = itemDictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! NSDictionary
print(javaScriptValues)
//this works, gets correct data. how to assign to IBOutlet?
self.pageTitle = javaScriptValues["title"] as! String
NSLog("Title From JS Preprocessor: " + self.pageTitle)
/*===================================*/
//Reference to property 'bookmarkTitle' in closure requires explicit 'self.' to make capture semantics explicit
//bookmarkTitle.text = self.pageTitle
//when the line below is used, the action extension fails to even open
self.bookmarkTitle.text = self.pageTitle
/*===================================*/
}
}
}
}
}
try to change the textfield text in Main Queue like this:
DispatchQueue.main.async {
// work that impacts the user interface
self.bookmarkTitle.text = self.pageTitle
}

Creating a profile which can be used everywhere in the app

I created an UIViewController which has two UITexFields and an UIImageView inside of it.
What I want it to be is a profile page, which provides information usable everywhere in the app.
What I tried to do is the following:
I created a Class with this code (based on Apple's tutorial on creating apps):
import UIKit
Class ProfilClass: NSObject, NSCoding {
//MARK: Properties
var bild: UIImage?
var vorname: String
var geburt: String
//MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("profil")
//MARK: Types
struct propKey {
static let bildKey = "bild"
static let vornameKey = "vorname"
static let geburtKey = "geburt"
}
//MARK: Initialization
init?(bild: UIImage?, vorname: String, geburt: String){
self.bild = bild
self.vorname = vorname
self.geburt = geburt
super.init()
if vorname.isEmpty || geburt.isEmpty {
return nil
}
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(bild, forKey: propKey.bildKey)
aCoder.encodeObject(vorname, forKey: propKey.vornameKey)
aCoder.encodeObject(geburt, forKey: propKey.geburtKey)
}
required convenience init?(coder aDecoder: NSCoder){
let bild = aDecoder.decodeObjectForKey(propKey.bildKey) as? UIImage
let vorname = aDecoder.decodeObjectForKey(propKey.vornameKey) as! String
let geburt = aDecoder.decodeObjectForKey(propKey.geburtKey) as! String
self.init(bild: bild, vorname: vorname, geburt: geburt)
}
}
I try to use this Class inside of my UIViewController:
override func viewDidLoad() {
[...]
if let profil = profil{
vornameLabel.text = profil.vorname
vornameField.text = profil.vorname
profilePic.image = profil.bild
geburtstagsLabel.text = profil.geburt
geburtstagField.text = profil.geburt
}
}
And when a save button is tapped:
#IBAction func butTap(sender: UIBarButtonItem) {
let vorname = vornameField.text
let geburt = geburtstagField.text
let bild = profilePic.image
profil = ProfilClass(bild: bild, vorname: vorname!, geburt: geburt!)
}
But after I close the UIViewController by going back to another one and reopen it, all the information is lost.
I don't know how to get the information again (I assume it is saved somewhere).
Can anyone help me?
public class MyViewState : NSObject{
static var isFromLogin = false
static var isFromCQ = false
}
add above to anywhere in any view controller it will accessible everywhere like
MyViewState.isFromLogin
make a shared class like following this will save data all the time
Class ProfilClass: NSObject, NSCoding {
static let sharedInstance = ProfilClass()
// here methods etc will go
}
and
ProfilClass.sharedInstance.(properties or method)
If you want data after app relaunched too , then save this to Userdefaults and load this class again and access anywhere in the app
Your mistake comes right at the end:
I assume it is saved somewhere
It is not, unless you ask for it to be saved. You need to use NSUserDefaults or NSKeyedArchiver to write your object. You've written all the code required to make that work, now you just need to do the reading and writing.
For example, to write your saved data you'll need something like this:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(profil, forKey: "SavedUserProfile")
I can't comment on reading because you have limited the code you posted:
[...]
if let profil = profil{
That is where your reading code should happen. I'm guessing(!) you're doing something like this:
let defaults = NSUserDefaults.standardUserDefaults()
if let profil = defaults.objectForKey("SavedUserProfile") as? ProfilClass
As you're following a specific Apple tutorial: You might find it easier just to continue following there rather than trying to use a different solution. Specifically, you need to continue onto the heading "Save and Load the Meal List", which is where the actual saving and loading happens. Apple writes the data to disk rather than user defaults, so you should follow along.
To be clear: the code you've written only enables your object to be saved and loaded. It doesn't actually do the saving.
var isFromLogin = false
var isFromCQ = false
Class ProfilClass: NSObject, NSCoding {.......}
add above to class it will accessible everywhere publicly . OR also create .swift file and declare constant variable to access globally

Swift Delegate in own class

I am currently trying to write my first Swift Mac application. Currently I have hard times refactoring some code into another class.
Current Status:
import Cocoa
class TestClass: NSObject, NSTextStorageDelegate {
#IBOutlet var codeTextView: NSTextView!
var syntaxParser:TRexSyntaxKitParser?
var textStorage : NSTextStorage!
init(syntaxParser:TRexSyntaxKitParser, textView:NSTextView) {
self.syntaxParser = syntaxParser
super.init()
if let textViewStorage = textView.textStorage {
self.textStorage = textViewStorage
self.textStorage.delegate = self
}
}
func textStorageDidProcessEditing(notification: NSNotification) {
let inputString = self.textStorage.string
let wholeRange = NSMakeRange(0, count(inputString))
self.textStorage.removeAttribute(NSForegroundColorAttributeName, range:wholeRange)
let attributes = self.syntaxParser!.parse(inputString)
print("Attributes: \(attributes)")
for attribDict: [String:AnyObject] in attributes {
let range = NSMakeRange(attribDict["rangeStart"] as! Int, attribDict["rangeLength"] as! Int)
self.textStorage.addAttribute(attribDict["attributeKey"] as! String, value:NSColor(hexString: attribDict["color"] as! String)!, range:range)
}
}
}
and this is how i call this class:
import Cocoa
class CodeEditorViewController: NSViewController {
#IBOutlet var codeTextView: NSTextView!
var syntaxParser:TRexSyntaxKitParser?
override func viewDidLoad() {
super.viewDidLoad()
self.syntaxParser = TRexSyntaxKitParser(language:"latex",theme:"classic")
let testClass = TestClass(syntaxParser: self.syntaxParser!, textView: self.codeTextView)
codeTextView.lnv_setUpLineNumberView()
}
but this produces the following error:
[NSFont textStorageDidProcessEditing:]: unrecognized selector sent to instance
I do not see where I would call the delegate method from NSFont ?
So to be precise: How can I refactor the first class into two different one ?
Think about the memory management of this line:
let testClass = TestClass(syntaxParser: self.syntaxParser!, textView: self.codeTextView)
testClass is a local variable. So what happens to your brand new TestClass instance? It comes into existence and immediately vanishes in a puff of smoke when viewDidLoad comes to an end.
Thus, you now have a delegate pointing at an object that does not exist. Hence, the crash.
Solution: make testClass something that will persist long enough to do you some good - like, an instance property of your view controller. That will give you exactly the refactoring you are after (this is a standard design pattern).

Swift: Set UITextField.text from class (retrieved value from SQLite)

I am using Swift with SQLite.swift. I have the following UIViewController:
class LoginViewController: UIViewController {
#IBOutlet weak var emailField: UITextField!
func setEmailAddress(email:String){
emailField.text = email
}
override func viewDidLoad() {
MySQLite().updateLatestEmailAddressFromUserTable() // breaks here (email is in console, though...)
}
}
Then I am trying to update it's value (through the setEmailAddress function) from another class:
class MySQLite {
func updateLatestEmailAddressFromUserTable(){
let dbPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first as String
let db = Database("\(dbPath)/db.sqlite3")
let users = db["users"]
let id = Expression<Int>("id")
let email = Expression<String>("email")
let time = Expression<Int>("time")
for user in users.limit(1).order(time.desc) {
println(user[email]) // this works, correctly outputs in console: email#domain.com
LoginViewController().setEmailAddress(user[email]) // breaks here
}
}
}
above code gives me the following error
fatal error: unexpectedly found nil while unwrapping an Optional value
To explain a little further: I am retrieving the most recent entry in SQLite table to get the user's email address and update the text field in the login view controller. This allows for easier log in for returning users.
I have been struggling with this for over 2 hours now and trying various things. The main problem I believe is that when I try to simply return the email address as string from my second function and set the field directly from LoginViewController, it doesn't work (SQLite related code was not "executed" yet I believe).
possibly related thread (Obj-C):
set UITextField.text from another class
Here whats happening LoginViewController().setEmailAddress(user[email]) creates new instance of LoginViewController which is not same as your current LoginViewController.
Why don't you make protocol and define as delegate in MySQLite
And LoginViewController will have implementation of update method. Pass the delegate to MySqlite
In MySQLite when you get the value form database call the delegate update method.
Example
MySQLite
protocol loginDelegate
{
func update(NSString)
}
class MySQLite {
var delegate:loginDelegate?
func updateLatestEmailAddressFromUserTable(){
let dbPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first as String
let db = Database("\(dbPath)/db.sqlite3")
let users = db["users"]
let id = Expression<Int>("id")
let email = Expression<String>("email")
let time = Expression<Int>("time")
for user in users.limit(1).order(time.desc) {
println(user[email]) // this works, correctly outputs in console: email#domain.com
if((delegate) != nil)
{
delegate?.update("example#example.com")
}
}
}
}
class LoginViewController: UIViewController,loginDelegate {
#IBOutlet weak var emailField: UITextField!
func setEmailAddress(email:String){
emailField.text = email
}
override func viewDidLoad() {
var mySQLite: MySQLite=LoginClass();
mySQLite.delegate=self;
[mySQLite .updateLatestEmailAddressFromUserTable()];
}
func update(email: NSString) {
println(email);
emailField.text = email
}
}
Make sure that the view which has the emailField has been instantiated on the screen.
#IBOutlet weak var emailField: UITextField!
This is an optional, which will be nil until the storyboard or nib for it is loaded. I assume OnBoardingRegistrationFormController is an instance of your LoginViewController class?
I see you've accepted an answer, but in this case creating a protocol is likely overkill. If sqlite is your model, why not just have the function return a value, and then you can assign the value to the text field in the controller. ex.
class LoginViewController: UIViewController {
#IBOutlet weak var emailField: UITextField!
override func viewDidLoad() {
emailField.text = MySQLite().updateLatestEmailAddressFromUserTable()
}
}
class MySQLite {
func updateLatestEmailAddressFromUserTable() -> String{
let dbPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first as String
let db = Database("\(dbPath)/db.sqlite3")
let users = db["users"]
let id = Expression<Int>("id")
let email = Expression<String>("email")
let time = Expression<Int>("time")
for user in users.limit(1).order(time.desc) {
println(user[email]) // this works, correctly outputs in console: email#domain.com
return user[email]
}
}
}
The issue is that LoginViewController's view isn't loaded when you try to assign a text to the textField. i.e: emailField is nil and unwrapping nil values leads to a runtime crash (since the outlet has not been connected to it's storyboard/xib counterpart).

Resources