I've spent the past 3 days trying to figure this out. I can easily do what I want to do in Java but as soon as I try to do it in Swift, Xcode gives me a hard time.
In this case, I am trying to retrieve a boolean object from Parse that will tell me whether the user's email has been verified. For some reason, whenever I tell to code to check to see if the object is false, checkEmail is apparently nil. Also worth noting, if I println(checkEmail) right after var checkEmail = User["emailVerified"] as Bool I get the correct boolean value (true or false).
Its looks like as soon as the code leaves the query function, the value for checkEmail is lost.
Any ideas on what I'm doing wrong?
import UIKit
class RegisterEmail: UIViewController {
var checkEmail: Bool?
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func shouldPerformSegueWithIdentifier(identifier: String!, sender: AnyObject!) -> Bool {
if identifier == "passEmail" {
var query = PFUser.query()
query.getObjectInBackgroundWithId("vFu93HatwL") {
(User: PFObject!, error: NSError!) -> Void in
if error == nil {
NSLog("%#", User)
} else {
NSLog("%#", error)
}
let checkEmail = User["emailVerified"] as Bool
println(checkEmail) //I get the correct value here
}
println(checkEmail) //The value is lost here
if (checkEmail == false) {
let alert = UIAlertView()
alert.title = "Error"
alert.message = "The email you have provided has not been verified."
alert.addButtonWithTitle("Dismiss")
alert.show()
return false
}
else {
return true
}
}
// by default, transition
return true
}
}
You're not assigning the new value to the object property, you're assigning it to a local variable. Get rid of the let keyword, which declares a new local variable, in:
let checkEmail = User["emailVerified"] as Bool
Related
Let say we have an object:
#:checkDirty
class Test {
var a:Int;
var b(default, default):String;
var c(get, set):Array<Int>;
public function new() {
...
}
public function get_c() {
...
}
public function set_c(n) {
...
}
}
Could we write a macro checkDirty so that any change to field/properties would set property dirty to true. Macro would generate dirty field as Bool and clearDirty function to set it to false.
var test = new Test();
trace(test.dirty); // false
test.a = 12;
trace(test.dirty); // true
test.clearDirty();
trace(test.dirty); //false
test.b = "test"
trace(test.dirty); //true
test.clearDirty();
test.c = [1,2,3];
trace(test.dirty); //true
Just to note - whenever you consider proxying access to an object, in my experience, there are always hidden costs / added complexity. :)
That said, you have a few approaches:
First, if you want it to be pure Haxe, then either a macro or an abstract can get the job done. Either way, you're effectively transforming every property access into a function call that sets the value and also sets dirty.
For example, an abstract using the #:resolve getter and setter can be found in the NME source code, replicated here for convenience:
#:forward(decode,toString)
abstract URLVariables(URLVariablesBase)
{
public function new(?inEncoded:String)
{
this = new URLVariablesBase(inEncoded);
}
#:resolve
public function set(name:String, value:String) : String
{
return this.set(name,value);
}
#:resolve
public function get(name:String):String
{
return this.get(name);
}
}
This may be an older syntax, I'm not sure... also look at the operator overloading examples on the Haxe manual:
#:op(a.b) public function fieldRead(name:String)
return this.indexOf(name);
#:op(a.b) public function fieldWrite(name:String, value:String)
return this.split(name).join(value);
Second, I'd just point out that if the underlying language / runtime supports some kind of Proxy object (e.g. JavaScript Proxy), and macro / abstract isn't working as expected, then you could build your functionality on top of that.
I wrote a post (archive) about doing this kind of thing (except for emitting events) before - you can use a #:build macro to modify class members, be it appending an extra assignment into setter or replacing the field with a property.
So a modified version might look like so:
class Macro {
public static macro function build():Array<Field> {
var fields = Context.getBuildFields();
for (field in fields.copy()) { // (copy fields so that we don't go over freshly added ones)
switch (field.kind) {
case FVar(fieldType, fieldExpr), FProp("default", "default", fieldType, fieldExpr):
var fieldName = field.name;
if (fieldName == "dirty") continue;
var setterName = "set_" + fieldName;
var tmp_class = macro class {
public var $fieldName(default, set):$fieldType = $fieldExpr;
public function $setterName(v:$fieldType):$fieldType {
$i{fieldName} = v;
this.dirty = true;
return v;
}
};
for (mcf in tmp_class.fields) fields.push(mcf);
fields.remove(field);
case FProp(_, "set", t, e):
var setter = Lambda.find(fields, (f) -> f.name == "set_" + field.name);
if (setter == null) continue;
switch (setter.kind) {
case FFun(f):
f.expr = macro { dirty = true; ${f.expr}; };
default:
}
default:
}
}
if (Lambda.find(fields, (f) -> f.name == "dirty") == null) fields.push((macro class {
public var dirty:Bool = false;
}).fields[0]);
return fields;
}
}
which, if used as
#:build(Macro.build())
#:keep class Some {
public function new() {}
public var one:Int;
public var two(default, set):String;
function set_two(v:String):String {
two = v;
return v;
}
}
Would emit the following JS:
var Some = function() {
this.dirty = false;
};
Some.prototype = {
set_two: function(v) {
this.dirty = true;
this.two = v;
return v;
}
,set_one: function(v) {
this.one = v;
this.dirty = true;
return v;
}
};
I'm currently creating a News-Feed-Reader App with SwiftUI.
I'm fetching the feed-items and storing them in CoreData
I'd like to display the objects in a List containing NavigationLinks to a Detail View and automatically mark them as read when clicking on them.
I'm currently fetching the objects and putting them in a ObservableObject.
This is the ObservableObject class:
final class FeedItem: ObservableObject, Hashable {
static func == (lhs: FeedItem, rhs: FeedItem) -> Bool {
return lhs.item.pubDate == rhs.item.pubDate && lhs.item.articleUrl == rhs.item.articleUrl
}
func hash(into hasher: inout Hasher) {
hasher.combine(item.articleUrl)
}
let objectWillChange = PassthroughSubject<Void, Never>()
init(item: NewsItem) {
self.item = item
}
// NewsItem is the Managed Object
var item: NewsItem
var bookmarked: Bool {
set {
//This function fetches the Object and marks it as bookmarked
DatabaseManager().markAs(item: item, .read, newValue)
self.item.bookmarked = newValue
}
get {
self.item.bookmarked
}
}
var read: Bool {
set {
//This function fetches the Object and marks it as read
DatabaseManager().markAs(item: item, .read, newValue)
self.item.read = newValue
}
get {
self.item.read
}
}
}
In the moment Im creating an environment Object (EO) containing an array of all ObservableObjects
This EO is passed down to the list and whenever Im clicking on an item Im setting its read value to true thereby changing the Core Data Object.
This is the list:
#EnvironmentObject var feed: // THe array of ObservableObjects
List() {
ForEach(feed.items.indices, id:\.self) { i in
Button(action: {
self.feed.items[i].read = true
self.selectedItem = i
self.showDetail = true
}) {
ListFeedItem(item: self.$feed.items[i])
}
}
}
This method is quite slow. Whenever I'm opening the Detail View and going back a few seconds later the List-Item takes multiple seconds to refresh.
Any ideas on how I could improve this?
I would like to have the following functionality in my app:
When I type the DNA sequence (string) in the NSTextView window at the same time in my TableView for each enzyme (each of them representing small string) user immediately see the number of found sites (string) corresponding to each enzyme (0 or any number).
I have a function, which I can use to find all possible locations (returning NSRanges array) of string in string. In my case this will be to find in DNA sequence (string) all possible sites (strings NSRanges) corresponding for each enzyme.
Thus, one more time, question is how to implement this function: at the time of typing a string to find all sites (in form of array of NSRanges) in this string and put the numbers found site in table accordingly for each enzyme.
In other words, the function returning NSRanges array for positions of enzymes sites should start automatically.
Update
I am new in cocoa and after suggestions from R Menke (I have putted his code lines below in the code) I have more probably stupid questions. I have one controller class as subclass of NSWindowController. I cannot put code from R Menke to this class (see errors below). And, in my controller class I have my NSTextView where user will type the text as #IBOutlet, should I use this? Should I make another controller file ? Below the code and errors.
import Cocoa
//Error. Multiple inheritance from classes 'NSWindowController' and 'NSViewController'
class AllUnderControl: NSWindowController, NSViewController,NSTextViewDelegate
{
override var windowNibName: String?
{
return "AllUnderControl"
}
override func windowDidLoad() {
super.windowDidLoad()
}
//Error. Instance member 'inputDnaFromUser' cannot be used on type 'AllUnderControl'
var textView = inputDnaFromUser(frame: CGRectZero)
//Error. Method does not override any method from its superclass
override func viewDidLoad() {
textView.delegate = self
}
func textDidChange(notification: NSNotification) {
// trigger your function
}
#IBOutlet var inputDnaFromUser: NSTextView!
Update 2
After reading the description of two controllers: NSWindowController and NSViewController I have made the following changes below. Is it correct for triggering function ?
import Cocoa
class AllUnderControl: NSWindowController, NSTextViewDelegate
{
override var windowNibName: String?
{
return "AllUnderControl"
}
override func windowDidLoad() {
super.windowDidLoad()
inputDnaFromUser.delegate = self
}
func textDidChange(notification: NSNotification) {
// trigger your function
}
#IBOutlet var inputDnaFromUser: NSTextView! = NSTextView(frame: CGRectZero)
If you are asking: "how do I trigger a function when someone types in an NSTextView?"
Implement an NSTextViewDelegate set the delegate of your NSTextView to self and trigger your function inside textDidChange. The NSTextViewDelegate has a set of functions that will be triggered by user interaction. So code inside of them will be executed when the corresponding action happens.
I would suggest using an NSViewController for this and not an NSWindowController. A NSWindowController is used to manage NSViewControllers. NSViewControllers are a better place for things like buttons and textfields.
NSWindowController vs NSViewController
class ViewController: NSViewController, NSTextViewDelegate {
#IBOutlet var inputDnaFromUser: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
inputDnaFromUser.delegate = self
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func textDidChange(notification: NSNotification) {
print("editing stuff")
}
}
If you are asking: "how can I find all occurrences of a string in another string?"
This will return an array of ranges. The count of that array is obviously the number of occurrences.
Another option is to use enumerateSubstringsInRange as stated in the answer by #Russel. I just always preferred to write my own loop.
let string = "The sky is blue today, super blue"
let searchString = "blue"
var ranges: [NSRange] = []
var copyString = NSMutableString(string: string)
while copyString.containsString(searchString) {
ranges.append(copyString.rangeOfString(searchString))
guard let lastRange = ranges.last else {
break
}
var replaceString = ""
for _ in 0..<lastRange.length { replaceString += "$" } // unnalowed character
copyString.replaceCharactersInRange(lastRange, withString: replaceString)
}
As suggested in the comments:
A faster method.
let string : NSString = "The sky is blue today, super blue"
let searchString = "blue"
var ranges: [NSRange] = []
var searchRange : NSRange = NSRange(location: 0, length: string.length)
var lastFoundRange : NSRange = string.rangeOfString(searchString, options: NSStringCompareOptions.LiteralSearch, range: searchRange)
while lastFoundRange.location != NSNotFound {
ranges.append(lastFoundRange)
let searchRangeLocation = lastFoundRange.location + lastFoundRange.length
let searchRangeLength = string.length - searchRangeLocation
searchRange = NSRange(location: searchRangeLocation, length: searchRangeLength)
lastFoundRange = string.rangeOfString(searchString, options: NSStringCompareOptions.LiteralSearch, range: searchRange)
}
You will be wanting to do this on a background queue. But this gets tricky quickly. An enzyme can be one kind one moment and change the next. So you will need to do all the work with every character typed.
One possible solution is to cancel each ongoing search when a character is typed. If it finished before typing the next character you get results.
This is an iOS word highlighter I wrote that implements this logic. Except for the use of UIColor everything is pure Foundation. So easy to change it to Cocoa.
You will want to implement the UISearchResultsUpdating protocol to achieve this. It uses a UISearchController (introduced in iOS 8) which has to be added programmatically instead of through the storyboard, but don't worry, it's pretty straight-forward.
The searching is handled using the updateSearchResultsForSearchController delegate method which is called anytime the search bar text is changed. I tried to keep it pretty self-documenting but let me know if you have any questions. Depending on how many enzymes you care to search, this could get inefficient very quickly because you have to search for substring occurrences for each enzyme.
Cheers,
Russell
class YourTableViewController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
// Array of searchable enzymes
var enzymes: [String] = ["...", "...", "..."]
// Dictionary of enzymes mapping to an array of NSRange
var enzymeSites: [String : [NSRange]] = [String : [NSRange]]()
// Search controller
var enzymeSearchController = UISearchController()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.enzymeSearchController = UISearchController(searchResultsController: nil)
self.enzymeSearchController.dimsBackgroundDuringPresentation = true
// This is used for dynamic search results updating while the user types
// Requires UISearchResultsUpdating delegate
self.enzymeSearchController.searchResultsUpdater = self
// Configure the search controller's search bar
self.enzymeSearchController.searchBar.placeholder = "Enter DNA sequence"
self.enzymeSearchController.searchBar.sizeToFit()
self.enzymeSearchController.searchBar.delegate = self
self.definesPresentationContext = true
// Set the search controller to the header of the table
self.tableView.tableHeaderView = self.enzymeSearchController.searchBar
}
// MARK: - Search Logic
func searchEnzymeSites(searchString: String) {
// Search through all of the enzymes
for enzyme in enzymes {
// See logic from here: https://stackoverflow.com/questions/27040924/nsrange-from-swift-range
let nsEnzyme = searchString as NSString
let enzymeRange = NSMakeRange(0, nsEnzyme.length)
nsEnzyme.enumerateSubstringsInRange(enzymeRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in
if (substring == enzyme) {
// Update the enzymeSites dictionary by appending to the range array
enzymeSites[enzyme]?.append(substringRange)
}
})
}
}
// MARK: - Search Bar Delegate Methods
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
// Force search if user pushes button
let searchString: String = searchBar.text.lowercaseString
if (searchString != "") {
searchEnzymeSites(searchString)
}
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
// Clear any search criteria
searchBar.text = ""
// Force reload of table data from normal data source
}
// MARK: - UISearchResultsUpdating Methods
// This function is used along with UISearchResultsUpdating for dynamic search results processing
// Called anytime the search bar text is changed
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchString: String = searchController.searchBar.text.lowercaseString
if (searchString != "") {
searchEnzymeSites(searchString)
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.enzymeSearchController.active) {
return self.enzymeSites.count
} else {
// return whatever your normal data source is
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("userCell") as! UserCell
if (self.enzymeSearchController.active && self.enzymeSites.count > indexPath.row) {
// bind data to the enzymeSites cell
} else {
// bind data from your normal data source
}
return cell
}
// MARK: - UITableViewDelegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if (self.enzymeSearchController.active && self.searchUsers.count > 0) {
// Segue or whatever you want
} else {
// normal data source selection
}
}
}
I am using Parse.com and swift
I have an initial view controller that presents the parse.com login.
Once the login is complete and the objects are saved in the background I want to present my navigation controller's root view controller (first controller linked in the storyboard).
How is this done with all the asynchronous calls?
This is what I have but it jumps back to the login screen and doesn't
func signUpViewController(signUpController: PFSignUpViewController!, didSignUpUser user: PFUser!) {
currentUser = user as? User
currentUser!.isManager = false
var query = PFUser.query()
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if objects.count == 1 {
currentUser!.isManager = true
}
currentUser?.saveInBackgroundWithBlock({ (success: Bool, error: NSError!) -> Void in
if success == false || error != nil {
println(error)
} else {
currentCompany = Company()
currentCompany!.companyName = "My Company"
currentCompany!.saveInBackgroundWithBlock({ (success: Bool!, error: NSError!) -> Void in
if success == false {
println(error)
}
})
}
})
}
dismissViewControllerAnimated(true, completion: { () -> Void in
self.performSegueWithIdentifier("jumpFromInitialToMessages", sender: self)
// let vc = MessagesViewController()
// self.navigationController?.presentViewController(vc, animated: true, completion: nil)
})
}
If you want to initiate a UI transition/update inside the completion handler, you should insert a dispatch back to the main queue inside the completion handler closure:
var query = PFUser.query()
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
// do something
// when done, do some UI update, e.g. perform segue to another scene
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("identifier", sender: self)
}
}
This is an example with a simple query, but the idea works with any asynchronously called completion handler closure: Just dispatch the UI update back to the main queue from within the closure.
Ok, this question even mind boggled my Computer Science teacher because nothing we thought of worked.
Below is my code. Because I am retrieving data from Parse, there is a slight delay in actually getting that data. And computers being computers, the app keeps executing the code even if the Parse data hasn't been retrieved.
All I want to do is tell my app to stop executing code for, say 5 seconds, before continuing (this should allow the Parse data to be retrieved and catch up with the program).
I've tried using the sleep (time) function but it just stops the whole program for the time imputed (so the data retrieval from Parse is also put on hold). Also, putting in a useless for-loop does the exact same thing.
The reason why I ask this is because I am getting a nil value when I print test1.
Also, it looks like "No Results" is being printed so that means something is wrong with my fetching Core Data stuff....
import UIKit
import Darwin
import CoreData
class RegisterEmail: UIViewController {
var test1: Bool?
var userID: String!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func shouldPerformSegueWithIdentifier(identifier: String!, sender: AnyObject!) -> Bool {
if identifier == "passEmail" {
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Users")
request.returnsObjectsAsFaults = false
var results: NSArray = context.executeFetchRequest(request, error: nil)!
if(results.count > 0)
{
var res = results [0] as NSManagedObject
userID = res.valueForKey("userID") as String
}
var query = PFUser.query()
query.getObjectInBackgroundWithId(userID) {
(User: PFObject!, error: NSError!) -> Void in
if error == nil {
//NSLog("%#", User)
var checkEmail = User["emailVerified"] as Bool
println(checkEmail)
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var newEmail = NSEntityDescription.insertNewObjectForEntityForName("Email", inManagedObjectContext: context) as NSManagedObject
newEmail.setValue(checkEmail, forKey: "emailStatus")
context.save(nil)
} else {
NSLog("%#", error)
}
}
var appDel1:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context1:NSManagedObjectContext = appDel1.managedObjectContext!
var request1 = NSFetchRequest(entityName: "Email")
request1.returnsObjectsAsFaults = false
var results1: NSArray = context1.executeFetchRequest(request1, error: nil)!
if(results1.count > 0)
{
var res1 = results1 [0] as NSManagedObject
test1 = res1.valueForKey("emailVerified") as Bool
}
else
{
println("No results")
}
println (test1) //NIL VALUE
if (test1 == false) {
let alert = UIAlertView()
alert.title = "Error"
alert.message = "The email you have provided has not been verified."
alert.addButtonWithTitle("Dismiss")
alert.show()
return false
}
else {
return true
}
}
// by default, transition
return true
}
}
An arbitrary delay time can be achieved with
-(void)performSelector: withObject: afterDelay: (NSObject)
but this is not the most efficient way tho do this by any means. because fetching the data might only take a split second on one occasion and many seconds or never on another. Instead you would be better looking at a completion block for pushing the data to print.