I have built a ASCellNode and it works perfectly. However when I used a traditional UICollectionViewCell I used an TTTAttributedLabel with links.
I don't know how should I replicate this with AsyncDisplayKit
I can assign the attriubtedText from the TTTAttributedLabel to an ASTextNode but of course it doesn't keep the links. How could I efficiently do this. Bellow the example of my ASCellNode.
protocol EmailSentDelegator : class {
func callSegueFromCell(data object: JSON)
}
class EmailCellNode: ASCellNode, TTTAttributedLabelDelegate {
let cardHeaderNode: ASTextNode
var frameSetOrNil: FrameSet?
init(mailData: JSON) {
// Create Nodes
cardHeaderNode = ASTextNode()
super.init()
// Set Up Nodes
cardHeaderNode.attributedString = createAttributedLabel(mailData, self).attributedText
// Build Hierarchy
addSubnode(cardHeaderNode)
}
override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize {
var cardSize = CGSizeZero
cardSize.width = UIScreen.mainScreen().bounds.size.width - 16
// Measure subnodes
let cardheaderSize = cardHeaderNode.measure(CGSizeMake(cardSize.width - 56, constrainedSize.height))
cardSize.height = max(cardheaderSize.height,40) + subjectLabelSize.height + timeStampSize.height + emailAbstractSize.height + 30
// Calculate frames
frameSetOrNil = FrameSet(node: self, calculatedSize: cardSize)
return cardSize
}
override func layout() {
if let frames = frameSetOrNil {
cardHeaderNode.frame = frames.cardHeaderFrame
}
}
func attributedLabel(label: TTTAttributedLabel!, didSelectLinkWithTransitInformation components: [NSObject : AnyObject]!) {
self.delegate.callSegueFromCell(data: mailData)
}
func createAttributedLabel(mailData: JSON, cell: EmailCellNode) -> TTTAttributedLabel{
let senderName = mailData["From"]["Name"].string!
var recipients:[String] = []
for (key: String, subJson: JSON) in mailData["To"] {
if let recipientName = subJson["Name"].string {
recipients.append(recipientName)
}
}
var cardHeader = TTTAttributedLabel()
cardHeader.setText("")
cardHeader.delegate = cell
cardHeader.userInteractionEnabled = true
// Add sender to attributed string and save range
var attString = NSMutableAttributedString(string: "\(senderName) to")
let senderDictionary:[String:String] = ["sender": senderName]
let rangeSender : NSRange = (attString.string as NSString).rangeOfString(senderName)
// Check if recipients is nil and add undisclosed recipients
if recipients.count == 0 {
attString.appendAttributedString(NSAttributedString(string: " undisclosed recipients"))
let rangeUndisclosed : NSRange = (attString.string as NSString).rangeOfString("undisclosed recipients")
attString.addAttribute(NSFontAttributeName, value: UIFont(name: "SourceSansPro-Semibold", size: 14)!, range: rangeUndisclosed)
attString.addAttribute(NSForegroundColorAttributeName, value: UIColor.grayColor(), range: rangeUndisclosed)
} else {
// Add recipients (first 5) to attributed string and save ranges for each
var index = 0
for recipient in recipients {
if (index == 0) {
attString.appendAttributedString(NSAttributedString(string: " \(recipient)"))
} else if (index == 5){
attString.appendAttributedString(NSAttributedString(string: ", and \(recipients.count - index) other"))
break
} else {
attString.appendAttributedString(NSAttributedString(string: ", \(recipient)"))
}
index = index + 1
}
}
cardHeader.attributedText = attString
// Adding recipients and sender links with recipient object to TTTAttributedLabel
cardHeader.addLinkToTransitInformation(senderDictionary, withRange: rangeSender)
if recipients.count != 0 {
var index = 0
var position = senderName.length + 2
for recipient in recipients {
let recipientDictionary:[String: AnyObject] = ["recipient": recipient,"index": index ]
let rangeRecipient : NSRange = (attString.string as NSString).rangeOfString(recipient, options: nil, range: NSMakeRange(position, attString.length-position))
cardHeader.addLinkToTransitInformation(recipientDictionary, withRange: rangeRecipient)
index = index + 1
if (index == 5) {
let recipientsDictionary:[String: AnyObject] = ["recipients": recipients]
let rangeRecipients : NSRange = (attString.string as NSString).rangeOfString("and \(recipients.count - index) other")
cardHeader.addLinkToTransitInformation(recipientsDictionary, withRange: rangeRecipients)
}
position = position + rangeRecipient.length
}
}
return cardHeader
}
}
extension EmailCellNode {
class FrameSet {
let cardHeaderFrame: CGRect
init(node: EmailCellNode, calculatedSize: CGSize) {
var calculatedcardHeaderFrame = CGRect(origin: CGPointMake(senderPhotoFrame.maxX + 8, senderPhotoFrame.minY) , size: node.cardHeaderNode.calculatedSize)
cardHeaderFrame = calculatedcardHeaderFrame.integerRect.integerRect
}
}
}
I am not familiar with AsyncDisplayKit, but there are some issues with your use of TTTAttributedLabel:
You are initializing the label with TTTAttributedLabel(), which calls init. You must instead use the designated initializers initWithFrame: or initWithCoder:, as init will not properly initialize the links array and various other internal properties. In the latest release of TTTAttributedLabel, init is marked as unavailable.
You are assigning to the attributedText property. Please see this note in TTTAttributedLabel.h:
#bug Setting attributedText directly is not recommended, as it may cause a crash when attempting to access any links previously set. Instead, call setText:, passing an NSAttributedString.
You should never assign to the attributedText property.
I ended up using solely ASTextNode it doesn't have as many features at TTTAttributedLabel but enough for my need. Further more since it's a heavy ASCollectionView it was better to go fully Async. Bellow the an exemple of how I did this in a ASCellNode with the creation of the complexe ASTextNode.
here is the final result with clickable name passing JSON data through a segue.
here is a simplified version of building the NSAttributedString
func createAttributedLabel(mailData: JSON) -> NSAttributedString{
var recipients:[String] = []
for (key: String, subJson: JSON) in mailData["To"] {
if let recipientName = subJson["Name"].string {
recipients.append(recipientName)
}
}
// Add recipients to attributed string and save ranges for each
var index = 0
var position = senderName.length + 2
for recipient in recipients {
let recipientDictionary:[String: AnyObject] = ["recipient": recipient,"index": index ]
let recipientJSON = mailData["To"][index]
attString.appendAttributedString(NSAttributedString(string: ", \(recipient)"))
let rangeRecipient : NSRange = (attString.string as NSString).rangeOfString(recipient, options: nil, range: NSMakeRange(position, attString.length-position))
attString.addAttributes([
kLinkAttributeName: recipientJSON.rawValue,
NSForegroundColorAttributeName: UIColor.blackColor(),
NSFontAttributeName: UIFont(name: "SourceSansPro-Semibold", size: 14)!,
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleNone.rawValue],
range: rangeRecipient)
}
index = index + 1
return attString
}
end then to detect link taps. I had to transform my JSON in raw value to pass the data across.
func textNode(textNode: ASTextNode!, tappedLinkAttribute attribute: String!, value: AnyObject!, atPoint point: CGPoint, textRange: NSRange) {
// The node tapped a link; open it if it's a valid URL
if (value.isKindOfClass(NSDictionary)) {
var jsonTransferred = JSON(rawValue: value as! NSDictionary)
self.delegate.callSegueFromCellUser(data: jsonTransferred!)
} else {
var jsonTransferred = JSON(rawValue: value as! NSArray)
self.delegate.callSegueFromCellRecipients(data: jsonTransferred!)
}
}
I'm one of the primary maintainers of ASDK, and would love to help you address any challenges — feel free to open a GitHub issue on the project (even just for questions).
What features is ASTextNode lacking that you love about TTT? It does handle links, complete with centroid-based tap disambiguation between multiple, disjoint & line-wrapping links. There are probably missing features, but since the project is so widely used, I bet other developers would find it useful to add any that you find a need for.
That said, if you don't need the significant performance gains that come with moving text layout and rendering off the main thread, you can wrap TTT in an initWithViewBlock — or simply create & add the view directly, without node wrapping, inside of any node's -didLoad method. Normally with ASDK, wrapping views is not required (which is why I asked about ASTextNode).
Good luck with your work!
Related
I am building a tableView which displays a message when empty.
I'm using the really helpful answers on this question (Handling an empty UITableView. Print a friendly message) This has led me to a function:
func emptyMessage(message:String, viewController:UITableViewController) {
let VCSize = viewController.view.bounds.size
let messageLabel = UILabel(frame: CGRect(x:0, y:0, width:VCSize.width, height:VCSize.height))
messageLabel.text = message
messageLabel.textColor = UIColor.black
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .center;
messageLabel.font = UIFont(name: "Futura", size: 15)
messageLabel.sizeToFit()
viewController.tableView.backgroundView = messageLabel;
viewController.tableView.separatorStyle = .none;
}
I could call this in every table views data source like this :
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if projects.count > 0 {
return 1
} else {
TableViewHelper.EmptyMessage("You don't have any projects yet.\nYou can create up to 10.", viewController: self)
return 0
}
}
which would work. However I would rather not have to implement that repeatedly and instead have one custom tableview with a method in the data source asking what message you would like to add.
I've tried extending the TableView class or making a subclass of tableView but I think this isn't the solution. Instead I think the solution is to overwrite the UITableViewDataSource protocol but my knowledge of protocols and delegation isn't sufficient.
I hope i'm on the right track with this. And to clarify I could do it in the way mentioned above but i'm trying to override the functionality to make a smart solution where i'm not repeating myself.
There is a very good library :
https://github.com/dzenbot/DZNEmptyDataSet
This can be used for all types of containers like UITableView, UICollectionView etc.
After conforming to some DZNEmptyDataSetSource, DZNEmptyDataSetDelegate , you can simply implement these functions:
func title(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
let str = "Empty Data."
let attrs = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)]
return NSAttributedString(string: str, attributes: attrs)
}
func description(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
let str = "Any Details about empty data."
let attrs = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)]
return NSAttributedString(string: str, attributes: attrs)
}
Apart from that, you can add a button to to perform some actions. Please refer the library for more features.
(Swift)
So the problem is:
My app (which is a kind of calculator) is crashing when the user puts in the textfield things that can't be calculated.
For example, if he types " -4-.", the app won't be able to do the math.
So, a pattern must be followed.
The following characters are allowed: 1234567890.-
The minus sign can only be typed when it is the first character in the textfield and cannot be typed again.
The point can only be typed after a number, and cannot be typed again.
Well you would have to determine:
When the user clicks on a number/digit/character, you would have to do a:
//Goes at top of one of your classes
var decimalCount:Int = 0
//At location of tap for character
if(decimalCount < 1) {
textField.text += "."
decimalCount += 1
}
This ideology could be applied to "-" as well.
Some how i have understood your question. According to my assumption our task is to validate the input for proper math function.Ok here we go.
First of all declare a bool variable at top of your class
var isNonNumericCharactersAllowes = true
At first we need to make our textfield to respond according to user input.So add delegate to text field and add the following delegate method.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
//1. To make sure that this is applicable to only particular textfield add tag.
if textField.tag == 1 {
let char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
let isBackSpace = strcmp(char, "\\b")
//Helps to react only while typing and not while clearing text
if (isBackSpace != -92) {
let numbersOnly = NSCharacterSet(charactersInString: "1234567890")
let characterSetFromTextField = NSCharacterSet(charactersInString: string)
let Validate:Bool = numbersOnly .isSupersetOfSet(characterSetFromTextField)
if !Validate {
if isNonNumericCharactersAllowes {
isNonNumericCharactersAllowes = false
return true
}
return false
}
isNonNumericCharactersAllowes = true
}
}
return true
}
The above method stops unusual text entry's such as 0..012,--4,4++ etc..
Now while hitting calculate button we need to some validation.Add the following code in IBAction.
#IBAction func calculate(sender: AnyObject) {
let textContent:String!
textContent = textFieldTwo.text
var characterContainer = textContent.characters.map { String($0) }
let numbersOnly = NSCharacterSet(charactersInString: "1234567890")
let lastObjectOfString = NSCharacterSet(charactersInString: characterContainer.last!)
let Validate:Bool = numbersOnly .isSupersetOfSet(lastObjectOfString)
if !Validate {
characterContainer .removeLast()
textFieldTwo.text = characterContainer .joinWithSeparator("")
}
}
This above validation helps in removing things like 30+20+,4+4+, etc.. i.e removes unused operators at the end.
I'm creating a yik yak clone and I can't seem to see the messages I post in the textField(string) on Parse. Is there something wrong I'm doing in my code that's not letting it show up on Parse?
#IBAction func postPressed(sender: AnyObject) {
if(currLocation != nil){
let testObj = PFObject(className: "BubbleTest")
testObj["userName"] = PFUser.currentUser()?.username
//testObj["profileName"] = PFUser.valueForKey("profileName") as! String
//testObj["photo"] = PFUser.currentUser()?.valueForKey("photo") as! PFFile
testObj["textField"] = self.textField.text
testObj["location"] = PFGeoPoint(latitude: currLocation!.latitude , longitude: currLocation!.longitude)
testObj["count"] = 0
testObj["replies"] = 0
testObj.saveInBackground()
self.dismissViewControllerAnimated(true, completion: nil)
}
else {
alert()
}
The reason you are not seeing anything because you post it into the wrong class. According to the picture BubbleTest is the name of the class not YikYakTest
replace this line
let testObj = PFObject(className: "YikYakTest")
by
let testObj = PFObject(className: "BubbleTest")
your code should look like :
Note use saveInBackgroundWithBlock method so you could check if there is an error while saving
let testObj = PFObject(className: "BubbleTest")
let username = PFUser.currentUser()?.username
testObj["userName"] = username
testObj["textField"] = self.textField.text
testObj["Location"] = PFGeoPoint(latitude:currLocation.latitude , longitude: currLocation.longitude)
testObj["count"] = 0
testObj["replies"] = 0
testObj.saveInBackgroundWithBlock { (success:Bool, error :NSError?) -> Void in
if error == nil
{
print("detail is saved")
self.dismissViewControllerAnimated(true, completion: nil)
}
else
{
print("error")
}
}
when you are saving PFGeopoint coordinates save it into Location column not location
I know many developer friends of mine who ran into a similar issue. I myself had this problem as well, now resolved. So hopefully I can provide some insight from what I learned querying data from Parse:
Try changing the numberOfSectionsInTableView method to return 1 instead of 0 like so:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
You may need to also have some data structure to hold the users' posts (messages):
var userPosts:NSMutableArray! = NSMutableArray()
Also, your table view could then have as many rows as you will have posts stored in userPosts:
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return userPosts.count
}
In cellForRowAtIndexPath, replace this:
let object = PFObject(className: "BubbleTest")
WITH THIS:
let userPost : PFObject = self.posts.objectAtIndex(indexPath!.row) as! PFObject
...
cell.message.text = userPost.objectForKey("message") as! String
return cell
}
This will set the text of your custom cell's message property to whatever the user's message is (i.e.: "Testing 1 2").
Note: These steps aren't intended to be the only steps needed to solve your problem. It is meant to guide you in the right direction with some basic steps.
Hope that helps! :)
I'm developing an app and using Stripe SDK and Card.io SDK. What i want to happen is populate the STPPaymentCardTextField Card Number,Expiry Month and Year with Card.io scanned credit card value. I Tried:
var paymentField = STPPaymentCardTextField()
func userDidProvideCreditCardInfo(cardInfo: CardIOCreditCardInfo!, inPaymentViewController paymentViewController: CardIOPaymentViewController!) {
var scanViewController: CardIOPaymentViewController = CardIOPaymentViewController(paymentDelegate: self)
paymentField.cardNumber = cardInfo.cardNumber
paymentField.expirationMonth = cardInfo.expiryMonth
paymentField.expirationYear = cardInfo.expiryYear
paymentViewController.dismissViewControllerAnimated(true, completion: nil)
}
I'm having an error Cannot assign to the result of this expression for each paymentField append.
What do you think i can do with this? Thanks!
STPPaymentCardTextField fields are read-only and you can only "get" from those properties.
STPPaymentCardTextField is used to collect credit card details. In your case you are already doing that using CardIOCreditCardInfo. Once you have the credit card details you can assemble the data into an STPCardParams object.
Once you've collected the card number, expiration, and CVC, package them up in an STPCardParams object and invoke the createTokenWithCard: method on the STPAPIClient class.
Now your method may look like this...
func userDidProvideCreditCardInfo(cardInfo: CardIOCreditCardInfo!, inPaymentViewController paymentViewController: CardIOPaymentViewController!) {
let card: STPCardParams = STPCardParams()
card.number = info.cardNumber
card.expMonth = info.expiryMonth
card.expYear = info.expiryYear
card.cvc = info.cvv
STPAPIClient.sharedClient().createTokenWithCard(card, completion: {(result, error) -> Void in
if error == nil {
// Either save the card token with your customer data or charge them right away
//createBackendChargeWithToken(token)
}
else {
//handleError(error)
}
})
}
paymentViewController?.dismissViewControllerAnimated(true, completion: nil)
}
Update May be the right answer to your question.
import Stripe
class PaymentCardEditorField: STPPaymentCardTextField {
func setExistingCard(card: STPCardParams) {
replaceField("numberField", withValue: card.number!)
replaceField("expirationField", withValue: String(format: "%02d/%02d", card.expMonth, (card.expYear % 100)))
replaceField("cvcField", withValue: card.cvc!)
}
func replaceField(memberName: String, withValue value: String) {
let field = self.valueForKey(memberName) as! UITextField
let delegate = self as! UITextFieldDelegate
let len = field.text?.characters.count
if delegate.textField?(field, shouldChangeCharactersInRange: NSMakeRange(0, len!), replacementString: value) ?? false {
field.text = value
}
}
}
And then call the setExistingCard
func userDidProvideCreditCardInfo(cardInfo: CardIOCreditCardInfo!, inPaymentViewController paymentViewController: CardIOPaymentViewController!) {
let card: STPCardParams = STPCardParams()
card.number = info.cardNumber
card.expMonth = info.expiryMonth
card.expYear = info.expiryYear
card.cvc = info.cvv
paymentTextField.setExistingCard(card)
}
Works like a charm.
Follow this thread for potential update to Stripe SDK for in built support in the future.
https://github.com/stripe/stripe-ios/issues/127
I got a much cleaner answer from here
let cardParams = STPCardParams()
cardParams.number = "4242424242424242"
cardParams.expMonth = 07 // this data type is UInt and *not* Int
cardParams.expYear = 19 // this data type is UInt and *not* Int
cardParams.cvc = "123"
let paymentField = STPPaymentCardTextField()
paymentField.cardParams = cardParams //the paymentTextField will now show the cc #, exp, and cvc from above
You can get other test cards numbers like Mastercard, Amex, and Discover from here from Stripe
Current Behavior
My core data keeps track of the values for a UITableView list. Each row has a title and description. My core data is working for appending new records and later deleting them. Core data is also working great for editing the content of the existing records.
Problem/Question
I just added drag and drop functionality in the table. On the surface, it works perfectly. I can drag the top item to the bottom, middle to the top, etc. However, the new list order does not persist after app shutdown because the Core Data records aren't being updated.
I found a few tutorials on this but none seem to work with my code. I also spent a few hours trying to use and adapt my current Core Data skill set (update, delete, edit) to invent a solution. I have no sweet moves or code kung fu.
Should you choose to accept this mission, below are the details and code you might need.
Information
Coding in Swift
Using X-Code 6.4
Core Data Info:
File name is: CD_Model
Entity name: TodayTask
Attribute names: "name" and "desc"
Code:
Main list variable:
var todayTaskList = [NSManagedObject]()
ViewDidLoad for main list page with UITableView
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Break
//Load the list from Core Data
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName:"TodayTask")
var error: NSError?
let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObject]
if let results = fetchedResults {
todayTaskList = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
//Break
//This provides a variable height for each row
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80.0
//Break
//Part of code for cell drag and drop functionality
let longpress = UILongPressGestureRecognizer(target: self, action: "longPressGestureRecognized:")
tableView.addGestureRecognizer(longpress)
}
Table Setup
//***** ----- ***** ------ ***** ----- ***** ----- *****
//Table View & Cell Setup
//***** ----- ***** ------ ***** ----- ***** ----- *****
#IBOutlet weak var name_Label: UILabel!
#IBOutlet weak var desc_Label: UILabel!
//Tells the table how many rows it should render
//*Looks to the Core Data NSObject to count tasks
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todayTaskList.count
}
//Creates the individual cells. If the above function returns 3, this runs 3 times
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Setup variables
let cellIdentifier = "BasicCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
let task = todayTaskList[indexPath.row]
//Create table cell with values from Core Data attribute lists
cell.nameLabel!.text = task.valueForKey("name") as? String
cell.descLabel!.text = task.valueForKey("desc") as? String
//Make sure the row heights adjust properly
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80.0
return cell
}
And here is where my problem is, the drag and drop. This code works, but it is missing code that rearranges the core data. Without that code, any drag/drop re-ordering will reset when I close the gap:
//This function initiates the Drag & Drop code.
func longPressGestureRecognized(gestureRecognizer: UIGestureRecognizer) {
let longPress = gestureRecognizer as! UILongPressGestureRecognizer
let state = longPress.state
var locationInView = longPress.locationInView(tableView)
var indexPath = tableView.indexPathForRowAtPoint(locationInView)
struct My {
static var cellSnapshot : UIView? = nil
}
struct Path {
static var initialIndexPath : NSIndexPath? = nil
}
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as! CustomTableViewCell;
var dragCellName = currentCell.nameLabel!.text
var dragCellDesc = currentCell.descLabel.text
//Steps to take a cell snapshot. Function to be called in switch statement
func snapshopOfCell(inputView: UIView) -> UIView {
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0)
inputView.layer.renderInContext(UIGraphicsGetCurrentContext())
let image = UIGraphicsGetImageFromCurrentImageContext() as UIImage
UIGraphicsEndImageContext()
let cellSnapshot : UIView = UIImageView(image: image)
cellSnapshot.layer.masksToBounds = false
cellSnapshot.layer.cornerRadius = 0.0
cellSnapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0)
cellSnapshot.layer.shadowRadius = 5.0
cellSnapshot.layer.shadowOpacity = 0.4
return cellSnapshot
}
switch state {
case UIGestureRecognizerState.Began:
//Calls above function to take snapshot of held cell, animate pop out
//Run when a long-press gesture begins on a cell
if indexPath != nil {
Path.initialIndexPath = indexPath
let cell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell!
My.cellSnapshot = snapshopOfCell(cell)
var center = cell.center
My.cellSnapshot!.center = center
My.cellSnapshot!.alpha = 0.0
tableView.addSubview(My.cellSnapshot!)
UIView.animateWithDuration(0.25, animations: { () -> Void in
center.y = locationInView.y
My.cellSnapshot!.center = center
My.cellSnapshot!.transform = CGAffineTransformMakeScale(1.05, 1.05)
My.cellSnapshot!.alpha = 0.98
cell.alpha = 0.0
}, completion: { (finished) -> Void in
if finished {
cell.hidden = true
}
})
}
case UIGestureRecognizerState.Changed:
//Runs when the user "lets go" of the cell
//Sets CG Y-Coordinate of snapshot cell to center of current location in table (snaps into place)
//If the indexPath is not 0 AND is not the same as it began (didn't move)...
//Update array and table row order
var center = My.cellSnapshot!.center
center.y = locationInView.y
My.cellSnapshot!.center = center
if ((indexPath != nil) && (indexPath != Path.initialIndexPath)) {
swap(&todayTaskList[indexPath!.row], &todayTaskList[Path.initialIndexPath!.row])
tableView.moveRowAtIndexPath(Path.initialIndexPath!, toIndexPath: indexPath!)
Path.initialIndexPath = indexPath
}
default:
//Runs continuously as there's a long press recognized?
//Animates cell movement
//Completion block:
//Removes snapshot of cell, cleans everything up
let cell = tableView.cellForRowAtIndexPath(Path.initialIndexPath!) as UITableViewCell!
cell.hidden = false
cell.alpha = 0.0
UIView.animateWithDuration(0.25, animations: { () -> Void in
My.cellSnapshot!.center = cell.center
My.cellSnapshot!.transform = CGAffineTransformIdentity
My.cellSnapshot!.alpha = 0.0
cell.alpha = 1.0
}, completion: { (finished) -> Void in
if finished {
Path.initialIndexPath = nil
My.cellSnapshot!.removeFromSuperview()
My.cellSnapshot = nil
}
})
}
I am pretty sure the code I need would go inside the second case statement:
case UIGestureRecognizerState.Changed:
I also think the code I need would start with something like...
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context: NSManagedObjectContext = appDel.managedObjectContext!
But then is there a special code for rearranging? Do I have to delete and insert? If so, how?
HUGE thanks in advance to anyone who can help resolve this one!
Well first of all, you might find it easier to create classes for each entity so that you don't have to work with objects vaguely typed to NSManagedObject or read and cast with valueForKey(_:). In the solution below, I've included code samples for that.
So to solve your order problem, there are two things you could do:
1) Add a property that defines the order of your Task entity. This can be a simple as an NSNumber called displayOrder. Your fetch request can then order the results according to that property. Then, when your table cells are re-arranged, iterate through the task list and update the displayOrder property of each task to reflect the order in which they are being displayed. Save your managed object context and the next time your fetch request loads, it will order them accordingly.
class Task: NSManagedObject {
#NSManaged var name: NSString
#NSManaged var desc: NSString
#NSManaged var displayOrder: NSNumber
}
let fetchRequest = NSFetchRequest()
let sortDescriptor = NSSortDescriptor(key: "displayOrder", ascending: true )
fetchRequest.sortDescriptors = [ sortDescriptor ]
2) Create a CoreData entity that represents a list with a to-many relationship that stores each task entity in an ordered set. Then, when you add tasks to the set, they will be remain saved in the order you've added them.
class TaskList: NSManagedObject {
#NSManaged var tasks: NSOrderedSet?
}
class Task: NSManagedObject {
#NSManaged var name: NSString
#NSManaged var desc: NSString
#NSManaged var parentList: TaskList?
}
Update to answer remaining questions:
I highly recommend you use your own custom classes instead of NSManagedObject, but until you figure that part out here's what you can do to your code as is.
To update display order after rearranging or deleting:
func updateDisplayOrder() {
for i in 0..<todayTaskList.count {
let task = todayTaskList[i]
task.setValue( i, forKey: "displayOrder" )
}
}
To append a new task:
func addTask( task: NSManagedObject, displayOrder: Int ) {
todayTaskList.insert( task, atIndex: displayOrder )
updateDisplayOrder()
}