Swift Delegate in own class - ios

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).

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 - Passing custom objects as a parameter

I started learning Swift today and in my first test app I am getting this error:
TestClass is not convertible to AnotherClass
The following is the TestClass:
class TestClass : NSObject {
var parameter1 : String = ""
var parameter2 : String = ""
override init() {
super.init()
}
func createJob(parameter1: String, parameter2: String) -> TestClass {
self.parameter1 = parameter1
self.parameter2 = parameter2
return self;
}
}
And this is the AnotherClass:
class AnotherClass: NSObject {
private struct internalConstants {
static let test1 = "testData"
static let test2 = "testData2"
}
var current : String
override init() {
self.current = internalConstants.test1
super.init()
}
func executeTask(testClass : TestClass) {
if testClass.parameter1 == "abc" {
return;
}
}
}
And this is the ViewController where I am getting the compiler error:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let obj = TestClass()
AnotherClass.executeTask(obj)
}
}
AnotherClass.executeTask line is giving the compiler error.
The obj variable sent as a parameter on this line is highlighted by Xcode with the error
"TestClass is not convertible to AnotherClass".
In C# or Objective C it is allowed to pass custom objects as a parameter to another methods. How can I do it in Swift?
Let's correct first the TestClass. This is how you should init a class like that:
class TestClass : NSObject {
....
init(parameter1: String, parameter2: String) {
....
}
}
Much simpler. Now, going back to your problem,
"TestClass is not convertible to AnotherClass".
Take a look at it again. The line you've mentioned in your question. You are trying to do this:
let obj = TestClass()
AnotherClass.executeTask(obj)
This line, AnotherClass.executeTask(obj), is giving you an error because indeed executeTask() is an instance method. You could do three ways for that.
add static keyword to the func executeTask... So it becomes like this: static func executeTask(testClass : TestClass) {
Instead of static keyword, you could add class. It becomes like so: class func executeTask(....
OR, better if you just instantiate the AnotherClass. Make a new object of AnotherClass. How to instantiate? You tell me. But here:
let anotherClass = AnotherClass()
Either implement executeTask as a class function
class func executeTask(testClass : TestClass) {
if testClass.parameter1 == "abc" {
return;
}
}
or instantiate AnotherClass in vieweDidLoad
let obj = TestClass()
let another = AnotherClass()
another.executeTask(testClass: obj)
Note the slightly different call to executeTask with the argument name.
And there is really no reason for you to subclass NSObject as I see it.
I think it's best to keep is simple. Create an instance of AnotherClass inside of ViewController.
class ViewController: UIViewController {
// Create an instance of AnotherClass which lives with ViewController.
var anotherClass = AnotherClass()
override func viewDidLoad() {
super.viewDidLoad()
let obj = TestClass()
// Use the instance of AnotherClass to call the method.
anotherClass.executeTask(testClass: obj)
}
}

Sending a variable to be modified in another class?

How can one send a variable from a viewcontroller to be modified by another viewcontroller?
I've tried setting the variable in the performSegue, but it does not get modified.
Sample code:
class VC1: ViewController{
var myVar: MyVar
....
prepare(for segue:...) {
let nextVC = segue.destination as! VC2
nextVC.var = myVar
}
....
}
class VC2: ViewController {
var var: MyVar
....
override func viewDidLoad() {
super.viewDidLoad()
var = MyVar("newValue")
}
}
After this code is executed, the value of myVar in VC1 is not changed to the new value. I believe it only gets a shallow copy of myVar, not a deep copy.
Is what I want achievable in swift?
Classes in Swift are pass by reference, whereas structs are pass by value. Assuming that MyVar is a class, you need to modify the properties of the class, ie:
myVar.property = "xyz"
instead of defining a new instance of the class as you have done in your question.
When you set var = MyVar("newValue") it assign new instance to var.
Examine the results from the following code in a playground. It should give you more insight into what you should expect, without the complication of segues and controllers.
class Variable {
var name:String
init(_ nameString:String) {
name = nameString
}
}
class Sender {
var myVar = Variable("Start name")
func showChanges() {
let receiver = Receiver(myVar)
print(myVar.name)
receiver.modify()
print(myVar.name)
receiver.replace()
print(myVar.name)
}
}
class Receiver {
var received: Variable
init(_ variable:Variable) {
received = variable
}
func modify() {
received.name = "Changed name"
}
func replace() {
received = Variable("New variable")
}
}
let s = Sender()
s.showChanges()

fatal error.. when accessing variables from another class

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 –

Realm Swift EXC_BAD_ACCESS During Segue with Unpersisted Object

I've got an app that I've started adding Realm to and I think I must be doing something wrong because I keep running into EXC_BAD_ACCESS when passing unpersisted objects between view controllers.
Here's a stripped down version of my app.
class TodoTask: Object {
dynamic var name: String = ""
let steps = List<Step>()
convenience init(name: String) {
self.init()
self.name = name
}
}
class Step: Object {
dynamic var name: String = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
class TodoListController: UIViewController {
let todos = List<TodoTask>()
override func viewDidLoad() {
super.viewDidLoad()
var t1 = TodoTask(name: "Todo1")
let steps = [
Step("Do this"),
Step("Do that"),
]
t1.steps.appendContentsOf(steps)
var t2 = TodoTask(name: "Todo2")
let steps = [
Step("Do these"),
Step("Do those"),
]
t2.steps.appendContentsOf(steps)
todos.appendContentsOf([t1, t2])
// Display table of todos.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let detailsController = segue.destinationViewController as? TodoDetailViewController,
let selectedTask = getSelectedTask() {
detailsController.task = selectedTask
}
}
}
class TodoDetailViewController: UIViewController {
var task: TodoTask? // <<< EXC_BAD_ACCESS
// Display the task's steps.
}
Unfortunately, I can't figure out what triggers the EXC_BAD_ACCESS and it happens intermittently. I didn't copy a stacktrace (d'oh!) but I remember it being in the C++ destructor for some sort of Row object. This is odd to me because there doesn't seem to be a database file in my Documents folder.
I'm pretty confident this is a Realm-based error because I experienced no weird crashes until I converted my plain Swift objects to Realm objects.
My only hunch is that I might be using List wrong since the warning This method can only be called during a write transaction. is in the comments of the appendContentsOf method. However, I was under the impression that I could use Realm objects that weren't stored in a Realm just like normal Swift objects.
Am I using Realm objects wrong? Is there anything else I can look into?
I'm on Realm 0.95.2.
Thanks!
Edit:
func getSelectedTask() -> TodoTask? {
if let index = taskTableView.indexPathForSelectedRow {
return tasks[index]
}
return nil
}
The issue is that a Realm List should not be created directly. This class is only used to manage to-many relationships on Object models. Instead, you should use a standard Swift array to store the unpersisted Realm Objects.
So switch:
let todos = List<TodoTask>()
to
let todos = [TodoTask]()

Resources