Referencing self in notification block in swift - swift

Because I'm sick of misspelling a selector name I figured I'd try to do some notification stuff with blocks instead of selectors.
I've put together some sample code that doesn't seem to work because I'm unable to access self
var currentString : String?
// Type alias the notificaitonBlock
typealias notificationBlock = (NSNotification?) -> ()
// in this case note is an NSNotification?
let strNotification : notificationBlock = { notification in
if let msg = notification?.object as? String {
self.currentString = msg
}
}
Assuming this code worked I would register it with:
nc.addObserverForName(UIDeviceOrientationDidChangeNotification,
object: self,
queue: NSOperationQueue.currentQueue(),
usingBlock: strNotification)
Xcode is giving me the following error:
NotificationTests.swift:49:4: 'NotificationTests -> () -> NotificationTests' does not have a member named 'currentString'
which implies self isn't pointing to the class but the block or something?

You could either use this when using an instance variable for the block:
lazy var block: (NSNotification?) -> () = { notification in
if let msg = notification?.object as? String {
self.currentString = msg
}
}
or inside a method call:
func registerObeserver() {
NSNotificationCenter.defaultCenter().addObserverForName(UIDeviceOrientationDidChangeNotification, object: self, queue: NSOperationQueue.currentQueue(), { notification in
if let msg = notification?.object as? String {
self.currentString = msg
}
})
}
As Martin R mentioned in the comments this probably has to do with one property of a class depending on another during initialization
Unlike this in javascript, self is not changing inside a closure

Related

How do I set the text of a label from outside of viewDidAppear?

I'm writing a Mac (Swift) application on Xcode which gets data from a command and asynchronously changes the stringValue of some text in the window. I already figured out the asynchronous part from here, but I can't seem to figure out how to actually change the text, since Xcode seems to require it to be in viewDidAppear. Unfortunately I can't put the function which runs the command in viewDidAppear since it is called by another file and needs to be a public func (as far as I know).
Here are a couple of methods I tried:
1. Call a function inside viewDidAppear which changes the text:
self.viewDidAppear().printText("testing!") // this part is where the "New Output" line is on the attached link above
...
override func viewDidAppear() {
func printText(_ string: String) {
textLabel.stringValue = string
}
}
Result: Value of tuple type '()' has no member 'printText' (on the first line)
2. Change an already-declared variable to the current message, then use Notification Center to tell viewDidAppear to change the text.
var textToPrint = "random text" // directly inside the class
let nc = NotificationCenter.default // directly inside the class
...
self.textToPrint = "testing!" // in place of the "New Output" line in the link above
self.nc.post(name: Notification.Name("printText"), object: nil) // in place of the "New Output" line in the link above
...
#objc func printText2() { // directly inside the class
textLabel.stringValue = textToPrint // directly inside the class
} // directly inside the class
...
override func viewDidAppear() {
nc.addObserver(self, selector: #selector(printText2), name: Notification.Name("printText"), object: nil)
}
For this one, I had to put printText2 outside of viewDidAppear because apparently selectors (for Notification Center) only work if you do that.
Result: NSControl.stringValue must be used from main thread only (on textLabel.stringValue line).
Also, the text never changes.
So I need to either somehow change the label's text directly from the asynchronous function, or to have viewDidAppear do it (also transmitting the new message).
...................................................................
Extra project code requested by Upholder of Truth
import Cocoa
class VC_image: NSViewController, NSWindowDelegate {
#IBOutlet var textLabel: NSTextField!
public func processImage(_ path: String) { // this function is called by another file
previewImage()
}
public func previewImage() {
if let path = Bundle.main.path(forResource: "bashscript", ofType: "sh") {
let task3 = Process()
task3.launchPath = "/bin/sh"
task3.arguments = [path]
let pipe3 = Pipe()
task3.standardOutput = pipe3
let outHandle = pipe3.fileHandleForReading
outHandle.readabilityHandler = { pipe3 in
if let line = String(data: pipe3.availableData, encoding: String.Encoding.utf8) {
// Update your view with the new text here
let messageToPrint = line.components(separatedBy: " ")
if (messageToPrint.count == 6) {
DispatchQueue.main.async {
self.textLabel.stringValue = "testing!"
}
}
} else {
print("Error decoding data: \(pipe3.availableData)")
}
}
task3.launch()
}
}
}

NSTouchBar integration not calling

I am integrating TouchBar support to my App. I used the how to from Rey Wenderlich and implemented everything as follows:
If self.touchBarArraygot filled the makeTouchBar() Method returns the NSTouchBar object. If I print out some tests the identifiers object is filled and works.
What not work is that the makeItemForIdentifier method not get triggered. So the items do not get created and the TouchBar is still empty.
Strange behavior: If I add print(touchBar) and a Breakpoint before returning the NSTouchBar object it works and the TouchBar get presented as it should (also the makeItemForIdentifier function gets triggered). Even if it disappears after some seconds... also strange.
#available(OSX 10.12.2, *)
extension ViewController: NSTouchBarDelegate {
override func makeTouchBar() -> NSTouchBar? {
if(self.touchBarArray.count != 0) {
let touchBar = NSTouchBar()
touchBar.delegate = self
touchBar.customizationIdentifier = NSTouchBarCustomizationIdentifier("com.TaskControl.ViewController.WorkspaceBar")
var identifiers: [NSTouchBarItemIdentifier] = []
for (workspaceId, _) in self.touchBarArray {
identifiers.append(NSTouchBarItemIdentifier("com.TaskControl.ViewController.WorkspaceBar.\(workspaceId)"))
}
touchBar.defaultItemIdentifiers = identifiers
touchBar.customizationAllowedItemIdentifiers = identifiers
return touchBar
}
return nil
}
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
if(self.touchBarArray.count != 0) {
for (workspaceId, data) in self.touchBarArray {
if(identifier == NSTouchBarItemIdentifier("com.TaskControl.ViewController.WorkspaceBar.\(workspaceId)")) {
let saveItem = NSCustomTouchBarItem(identifier: identifier)
let button = NSButton(title: data["name"] as! String, target: self, action: #selector(self.touchBarPressed))
button.bezelColor = NSColor(red:0.35, green:0.61, blue:0.35, alpha:1.00)
saveItem.view = button
return saveItem
}
}
}
return nil
}
}
self.view.window?.makeFirstResponder(self) in viewDidLoad() did solve the problem.

Swift Pusher example doing nothing

I am using the starter code Pusher provides, when I put it into a basic swift project and I send it a message nothing happens. I have the cocoa pod package installed as well.
The statement "data received" should print.
There are also no errors.
Are you initialising the Pusher object as a var? You need to maintain a strong reference to the object otherwise it can become deallocated.
As an example it should be something like:
class ViewController: UIViewController {
var pusher: Pusher!
override func viewDidLoad() {
super.viewDidLoad()
let options = PusherClientOptions(
host: .cluster("mycluster")
)
pusher = Pusher(
key: "app_key",
options: options
)
// subscribe to channel and bind to event
let channel = pusher.subscribe("my-channel")
let _ = channel.bind(eventName: "my-event", callback: { (data: Any?) -> Void in
if let data = data as? [String : AnyObject] {
if let message = data["message"] as? String {
print(message)
}
}
})
pusher.connect()
}
}

Singleton not accessible in Sirikit IntentHandler

I have a project that I want to add sirikit to. I added the intent and wanted to store values in my datastorage which is realm, when I tried to access the function that is used to create this task , I get an eeror. this is my code below
extension IntentHandler : INCreateTaskListIntentHandling {
public func handle(intent: INCreateTaskListIntent,
completion: #escaping (INCreateTaskListIntentResponse) -> Swift.Void) {
guard let title = intent.title else {
completion(INCreateTaskListIntentResponse(code: .failure, userActivity: nil))
return
}
CategoryFunctions.instance.createList(name: title.spokenPhrase,.....)
var tasks: [INTask] = []
if let taskTitles = intent.taskTitles {
let taskTitlesStrings = taskTitles.map {
taskTitle -> String in
return taskTitle.spokenPhrase
}
tasks = createTasks(fromTitles: taskTitlesStrings)
CategoryFunctions.instance.add(tasks: taskTitlesStrings, toList: title.spokenPhrase)
}
let response = INCreateTaskListIntentResponse(code: .success, userActivity: nil)
response.createdTaskList = INTaskList(title: title,
tasks: tasks,
groupName: nil,
createdDateComponents: nil,
modifiedDateComponents: nil,
identifier: nil)
completion(response)
}
}
this singlton instantiation works well in my app but I do not know why I get an error saying Use of unresolved identifier 'CategoryFunctions'
my CategoryFunctions singleton
class CategoryFunctions {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var database:Realm!
static let instance = CategoryFunctions()
.....
...
Select your file in xcode, on the right, choose the File Inspector, then under Target Membership, pick your Intent.

Getting data out of a firebase function in Swift [duplicate]

In my iOS app, I have two Firebase-related functions that I want to call within viewDidLoad(). The first picks a random child with .queryOrderedByKey() and outputs the child's key as a string. The second uses that key and observeEventType to retrieve child values and store it in a dict. When I trigger these functions with a button in my UI, they work as expected.
However, when I put both functions inside viewDidLoad(), I get this error:
Terminating app due to uncaught exception 'InvalidPathValidation', reason: '(child:) Must be a non-empty string and not contain '.' '#' '$' '[' or ']''
The offending line of code is in my AppDelegate.swift, highlighted in red:
class AppDelegate: UIResponder, UIApplicationDelegate, UITextFieldDelegate
When I comment out the second function and leave the first inside viewDidLoad, the app loads fine, and subsequent calls of both functions (triggered by the button action) work as expected.
I added a line at the end of the first function to print out the URL string, and it doesn't have any offending characters: https://mydomain.firebaseio.com/myStuff/-KO_iaQNa-bIZpqe5xlg
I also added a line between the functions in viewDidLoad to hard-code the string, and I ran into the same InvalidPathException issue.
Here is my viewDidLoad() func:
override func viewDidLoad() {
super.viewDidLoad()
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
pickRandomChild()
getChildValues()
}
Here is the first function:
func pickRandomChild () -> String {
var movieCount = 0
movieRef.queryOrderedByKey().observeEventType(.Value, withBlock: { (snapshot) in
for movie in snapshot.children {
let movies = movie as! FIRDataSnapshot
movieCount = Int(movies.childrenCount)
movieIDArray.append(movies.key)
}
repeat {
randomIndex = Int(arc4random_uniform(UInt32(movieCount)))
} while excludeIndex.contains(randomIndex)
movieToGuess = movieIDArray[randomIndex]
excludeIndex.append(randomIndex)
if excludeIndex.count == movieIDArray.count {
excludeIndex = [Int]()
}
let arrayLength = movieIDArray.count
})
return movieToGuess
}
Here is the second function:
func getChildValues() -> [String : AnyObject] {
let movieToGuessRef = movieRef.ref.child(movieToGuess)
movieToGuessRef.observeEventType(.Value, withBlock: { (snapshot) in
movieDict = snapshot.value as! [String : AnyObject]
var plot = movieDict["plot"] as! String
self.moviePlot.text = plot
movieValue = movieDict["points"] as! Int
})
return movieDict
)
And for good measure, here's the relevant portion of my AppDelegate.swift:
import UIKit
import Firebase
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UITextFieldDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
return true
}
I'm guessing Swift is executing the code not in the order I expect. Does Swift not automatically wait for the first function to finish before running the second? If that's the case, why does this pairing work elsewhere in the app but not in viewDidLoad?
Edit: The issue is that closures are not called in order.
I'm not sure what your pickRandomChild() and getChildValues() methods are, so please post them as well, but the way I fixed this type issue was by sending the data through a closure that can be called in your ViewController.
For example when I wanted to grab data for a Full Name and Industry I used this. This method takes a Firebase User, and contains a closure that will be called upon completion. This was defined in a class specifically for pulling data.
func grabDataDict(fromUser user: FIRUser, completion: (data: [String: String]) -> ()) {
var myData = [String: String]()
let uid = user.uid
let ref = Constants.References.users.child(uid)
ref.observeEventType(.Value) { (snapshot, error) in
if error != nil {
ErrorHandling.defaultErrorHandler(NSError.init(coder: NSCoder())!)
return
}
let fullName = snapshot.value!["fullName"] as! String
let industry = snapshot.value!["industry"] as! String
myData["fullName"] = fullName
myData["industry"] = industry
completion(data: myData)
}
}
Then I defined an empty array of strings in the Viewcontroller and called the method, setting the variable to my data inside the closure.
messages.grabRecentSenderIds(fromUser: currentUser!) { (userIds) in
self.userIds = userIds
print(self.userIds)
}
If you post your methods, however I can help you with those specifically.
Edit: Fixed Methods
1.
func pickRandomChild (completion: (movieToGuess: String) -> ()) {
var movieCount = 0
movieRef.queryOrderedByKey().observeEventType(.Value, withBlock: { (snapshot) in
for movie in snapshot.children {
let movies = movie as! FIRDataSnapshot
movieCount = Int(movies.childrenCount)
movieIDArray.append(movies.key)
}
repeat {
randomIndex = Int(arc4random_uniform(UInt32(movieCount)))
} while excludeIndex.contains(randomIndex)
movieToGuess = movieIDArray[randomIndex]
excludeIndex.append(randomIndex)
if excludeIndex.count == movieIDArray.count {
excludeIndex = [Int]()
}
let arrayLength = movieIDArray.count
// Put whatever you want to return here.
completion(movieToGuess)
})
}
2.
func getChildValues(completion: (movieDict: [String: AnyObject]) -> ()) {
let movieToGuessRef = movieRef.ref.child(movieToGuess)
movieToGuessRef.observeEventType(.Value, withBlock: { (snapshot) in
movieDict = snapshot.value as! [String : AnyObject]
var plot = movieDict["plot"] as! String
self.moviePlot.text = plot
movieValue = movieDict["points"] as! Int
// Put whatever you want to return here.
completion(movieDict)
})
}
Define these methods in some model class, and when you call them in your viewcontroller, you should be able to set your View Controller variables to movieDict and movieToGuess inside each closure. I made these in playground, so let me know if you get any errors.
Your functions pickRandomChild() and getChildValues() are asynchronous, therefore they only get executed at a later stage, so if getChildValues() needs the result of pickRandomChild(), it should be called in pickRandomChild()'s completion handler / delegate callback instead, because when one of those are called it is guaranteed that the function has finished.
It works when you comment out the second function and only trigger it with a button press because there has been enough time between the app loading and you pushing the button for the asynchronous pickRandomChild() to perform it action entirely, allowing getChildValues() to use its returned value for its request.