Animate CollectionViewCell after deleting item - ios

For my CollectionView I have this animation inside willDisplay :
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// Add animations here
let animation = AnimationFactory.makeMoveUpWithFade(rowHeight: cell.frame.height, duration: 0.5, delayFactor: 0.1)
let animator = Animator(animation: animation)
animator.animate(cell: cell, at: indexPath, in: collectionView)
}
This is how the animation works (I implemented it for CollectionView) if you need it for more info.
Probelm:
Inside my project the user can create and delete an item.
Right now the collectionView is not animating after deleting even though I am calling reloadData:
extension MainViewController: DismissWishlistDelegate {
func dismissWishlistVC(dataArray: [Wishlist], dropDownArray: [DropDownOption]) {
self.dataSourceArray = dataArray
self.dropOptions = dropDownArray
self.makeWishView.dropDownButton.dropView.tableView.reloadData()
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: nil)
}
}
This is where I call the delegate inside my other ViewController:
func deleteTapped(){
let alertcontroller = UIAlertController(title: "Wishlist löschen", message: "Sicher, dass du diese Wishlist löschen möchtest?", preferredStyle: .alert)
let deleteAction = UIAlertAction(title: "Löschen", style: .default) { (alert) in
DataHandler.deleteWishlist(self.wishList.index)
self.dataSourceArray.remove(at: self.currentWishListIDX)
self.dropOptions.remove(at: self.currentWishListIDX)
// change heroID so wishlist image doesnt animate
self.wishlistImage.heroID = "delete"
self.dismiss(animated: true, completion: nil)
// update datasource array in MainVC
self.dismissWishlistDelegate?.dismissWishlistVC(dataArray: self.dataSourceArray, dropDownArray: self.dropOptions)
}
let cancelAction = UIAlertAction(title: "Abbrechen", style: .default) { (alert) in
print("abbrechen")
}
alertcontroller.addAction(cancelAction)
alertcontroller.addAction(deleteAction)
self.present(alertcontroller, animated: true)
}
When creating the animation works just fine. This is how my createDelegateFunction looks like this:
func createListTappedDelegate(listImage: UIImage, listImageIndex: Int, listName: String) {
// append created list to data source array
var textColor = UIColor.white
if Constants.Wishlist.darkTextColorIndexes.contains(listImageIndex) {
textColor = UIColor.darkGray
}
let newIndex = self.dataSourceArray.last!.index + 1
self.dataSourceArray.append(Wishlist(name: listName, image: listImage, wishData: [Wish](), color: Constants.Wishlist.customColors[listImageIndex], textColor: textColor, index: newIndex))
// append created list to drop down options
self.dropOptions.append(DropDownOption(name: listName, image: listImage))
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: {
(result) in
// scroll to make newly added row visible (if needed)
let i = self.theCollectionView.numberOfItems(inSection: 0) - 1
let idx = IndexPath(item: i, section: 0)
self.theCollectionView.scrollToItem(at: idx, at: .bottom, animated: true)
})
}

Animation of IUCollectionView elements insertion and deletion is correctly done using finalLayoutAttributesForDisappearingItem(at:) and initialLayoutAttributesForAppearingItem(at:)
Little excerpt from Apple's documentation on finalLayoutAttributesForDisappearingItem(at:)
This method is called after the prepare(forCollectionViewUpdates:) method and before the finalizeCollectionViewUpdates() method for any items that are about to be deleted. Your implementation should return the layout information that describes the final position and state of the item. The collection view uses this information as the end point for any animations. (The starting point of the animation is the item’s current location.) If you return nil, the layout object uses the same attributes for both the start and end points of the animation.

I solved the problem by removing performBatchUpdates ... I have no idea why it works now and what exactly performBatchUpdates does but it works so if anyone wants to explain it to me, feel free :D
The final function looks like this:
func dismissWishlistVC(dataArray: [Wishlist], dropDownArray: [DropDownOption], shouldDeleteWithAnimation: Bool, indexToDelete: Int) {
if shouldDeleteWithAnimation {
self.shouldAnimateCells = true
self.dataSourceArray.remove(at: self.currentWishListIDX)
self.dropOptions.remove(at: self.currentWishListIDX)
// reload the collection view
theCollectionView.reloadData()
} else {
self.shouldAnimateCells = false
self.dataSourceArray = dataArray
self.dropOptions = dropDownArray
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: nil)
}
self.makeWishView.dropDownButton.dropView.tableView.reloadData()
}

Related

How to reload the indexPath of the collectionviewCell in Swift?

I'm currently working on an app that has a table-view-like collection view and some other view controllers. Basically, my question is how can I update the indexPath of each cell when one of the collection view cells is deleted.
I attached my view controller file below, but here is what's going on on the app.
When a user opens the table-view-like collection view (in EventCollectionVC), it reloads the data from a database and presents them on the collection view. I also added the code to the navigation bar button item that the user can change the collection view to the edit mode. While in the edit mode, a small ellipsis.circle (SF symbols) is displayed on the collection view cell. When a user taps the ellipsis.circle icon, it displays a new view controller (ModalVC) and lets the user select either delete or edit the cell. When the user selects delete, it shows an alert to delete the cell and delete the cell information with modal dismiss (which means the ModalVC is closed and the MyCollectionVC is displayed now).
Since I have to make the two view controllers (like getting cell information from EventCollectionVC and present in ModalVC) talk to each other, I need to use the indexPath.row to get the information of the cell. Before deleting the cells, the numbers of indexPath.row in the collection view is like
[0,1,2,3,4,5]
But, for example, after I delete the second (indexPath.row = 1) cell and when I try to delete another item, the indexPath becomes
[0,2,3,4,5]
and I can see the collection view's index is not refreshed.
So my question is how can I update/refresh the cell's indexPath.row value after I delete a cell from the collection view?
This is the code with some explanations.
import UIKit
class EvnetCollectionViewController: UIViewController {
var EventDataSource: EventDataSource! // <- this is a class for the Model, and it stores array or Events
let ListView = ListView() // view file
var collectionViewDataSource: UICollectionViewDiffableDataSource<Section, Event>?
var targetEventIndex = Int() // variable to store the index of the event when event cell is tapped
override func loadView() {
view = ListView
}
override func viewDidLoad() {
super.viewDidLoad()
configureNavItem()
setupCollectionView()
displayEvents()
}
func configureNavItem() {
navigationItem.rightBarButtonItem = editButtonItem
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if (editing){
ListView.collectionView.isEditing = true
} else {
ListView.collectionView.isEditing = false
}
}
func setupCollectionView() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Event> { cell, indexPath, Event in
var content = UIListContentConfiguration.cell()
content.text = Event.title
cell.contentConfiguration = content
let moreAction = UIAction(image: UIImage(systemName: "ellipsis.circle"),
handler: { _ in
let vc = EventActionModalViewController(); // when the user select the cell in edit mode, it displays action modal VC and then come back to this VC with dismiss later
vc.modalPresentationStyle = .overCurrentContext
self.targetEventIndex = indexPath.row // I need targetEvemtIndex when user selects delete event in EventActionModalVC, so just sotre value in here
})
let moreActionButton = UIButton(primaryAction: moreAction)
let moreActionAccessory = UICellAccessory.CustomViewConfiguration(
customView: moreActionButton,
placement: .trailing(displayed: .whenEditing, at: { _ in return 0 })
)
cell.accessories = [
.disclosureIndicator(displayed: .whenNotEditing),
.customView(configuration: moreActionAccessory)
]
}
collectionViewDataSource = UICollectionViewDiffableDataSource<Section, Event>(collectionView: ListView.collectionView) {
collectionView, indexPath, Event in
collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: Event)
}
}
func displayEvents() {
EventDataSource = EventDataSource()
EventDataSource.loadData() // get Events in db and sore in an array Events
populate(with: EventDataSource.Events)
}
func populate(with Events: [Event]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Event>()
snapshot.appendSections([.List])
snapshot.appendItems(Events)
collectionViewDataSource?.apply(snapshot)
}
func showDeleteAlert() {
let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in
self.EventDataSource.delete(at: targetEventIndex)
self.refreshList()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
self.showAlert(title: "Delete", message: nil, actions: [deleteAction, cancelAction], style: .actionSheet, completion: nil)
}
func refreshList() {
EventDataSource.loadData()
setupCollectionView() // since I write this code, it updates all of the indexPath in the collection view, but after deleting one item, the whole collection view is deleted and new collection view is reappeared.
populate(with: EventDataSource.Events)
}
}
I kinda know why this is happening. I only configure cell (in let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Event>...) once, so it won't update the cell information as well as its index path until I configure it again. But if I call setupCollectionView every after deleting one item, the whole collection view disappears and shows up again. Is it possible to reload the collection view list and updates its information without reloading the entire collection view?
Without writing setupCollectionView() in refreshList, the cell's indexPath is not refreshed and I get an error after I delete one cell and try to delete another one. So, I was wondering if there is a way to avoid recreating the whole collection view but update cells' indexPath when the user delete one of the cell in collection view.
I fixed the code in the refresh list function.
func refreshList() {
self.EventDataSource.loadData()
self.populate(with: self.EventDataSource.Events)
ListView.collectionView.reloadData()
}
I just needed to call reloadData after I populate all the data...

issue while populating String into textView

uploaded video (7 sec) for describing my problem
i have a textView embed in a tableView cell when we enter something on that textView the textview automatically increases its height according to entered content and also cell increases its size for doing this am using this approach:-
//tableViewCell
/// Custom setter so we can initialise the height of the text view
var textString: String {
get {
return textView?.text ?? ""
}
set {
if let textView = textView {
textView.text = newValue
textViewDidChange(textView)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Disable scrolling inside the text view so we enlarge to fitted size
textView?.scrollEnabled = false
textView?.delegate = self
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
textView?.becomeFirstResponder()
} else {
textView?.resignFirstResponder()
}
}
}
extension MultiLineTextInputTableViewCell: UITextViewDelegate {
func textViewDidChange(textView: UITextView) {
let size = textView.bounds.size
let newSize = textView.sizeThatFits(CGSize(width: size.width, height: CGFloat.max))
// Resize the cell only when cell's size is changed
if size.height != newSize.height {
UIView.setAnimationsEnabled(false)
tableView?.beginUpdates()
tableView?.endUpdates()
UIView.setAnimationsEnabled(true)
if let thisIndexPath = tableView?.indexPathForCell(self) {
tableView?.scrollToRowAtIndexPath(thisIndexPath, atScrollPosition: .Bottom, animated: false)
}
}
}
here everything is fine but i added a function for Forwarding any content and for this am using this approach :-
forwardNoteAction = UITableViewRowAction(style: .Normal, title: "Forward"){ action, index in
let cell = tableView.cellForRowAtIndexPath(indexPath) as! NotesTableViewCell
if self.searchBar.text == "" {
let object: PFObject = self.noteObjects.objectAtIndex(indexPath.row) as! PFObject
let postsObjID = object.objectId!
let query = PFQuery(className: "Notes")
query.fromLocalDatastore()
query.getObjectInBackgroundWithId(postsObjID) {
(objectViewes, error) -> Void in
if error != nil {
print(error)
} else {
self.forwardObject = object
print(self.forwardObject)
//object.pinInBackground()
self.performSegueWithIdentifier("forwardNoteSegue", sender: nil)
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
i am putting the content into a variable self.forwardObject and passing that variable with an segue forwardNoteSegue after passing self.forwardObject am using that content for filling my tableView (which is a form , consist of textViews and TextFields) but when am doing this my textView is not increasing its height according to content which is inside that textView ,
all i want is the textView should increase increase its height when we use "textView.text = copiedContent" same as when we enter anything into the textView manually
please for understanding my problem properly see the uploaded video (link is provided above)
if my question is not understandable than please let me know i'll fix it
thanks
You need to manually call textViewDidChange when you modify the content of you textview programatically.
This is documented in the UITextviewDelegate's description of the method:
The text view calls this method in response to user-initiated changes to the text. This method is not called in response to
programmatically initiated changes.

How to drag a static cell into tableView swift?

I have one tableView in my storyBoard where I added 4 static cell into it and my storyBoard look like:
I don't have any dataSource for this tableView because my cells are static.
And I use below code to drag a cell and it is working fine till I scroll a table.
import UIKit
class TableViewController: UITableViewController {
var sourceIndexPath: NSIndexPath = NSIndexPath()
var snapshot: UIView = UIView()
let longPress: UILongPressGestureRecognizer = {
let recognizer = UILongPressGestureRecognizer()
return recognizer
}()
override func viewDidLoad() {
super.viewDidLoad()
longPress.addTarget(self, action: "longPressGestureRecognized:")
self.tableView.addGestureRecognizer(longPress)
self.tableView.allowsSelection = false
}
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
// MARK: UIGestureRecognizer
func longPressGestureRecognized(gesture: UILongPressGestureRecognizer){
let state: UIGestureRecognizerState = gesture.state
let location:CGPoint = gesture.locationInView(self.tableView)
if let indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(location){
switch(state){
case UIGestureRecognizerState.Began:
sourceIndexPath = indexPath
let cell: UITableViewCell = self.tableView .cellForRowAtIndexPath(indexPath)!
//take a snapshot of the selected row using helper method
snapshot = customSnapshotFromView(cell)
//add snapshot as subview, centered at cell's center
var center: CGPoint = cell.center
snapshot.center = center
snapshot.alpha = 0.0
self.tableView.addSubview(snapshot)
UIView.animateWithDuration(0.25, animations: { () -> Void in
center.y = location.y
self.snapshot.center = center
self.snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05)
self.snapshot.alpha = 0.98
cell.alpha = 0.0
}, completion: { (finished) in
cell.hidden = true
})
case UIGestureRecognizerState.Changed:
let cell: UITableViewCell = self.tableView.cellForRowAtIndexPath(indexPath)!
var center: CGPoint = snapshot.center
center.y = location.y
snapshot.center = center
print("location \(location.y)")
//is destination valid and is it different form source?
if indexPath != sourceIndexPath{
//update data source
//I have commented this part because I am not using any dataSource.
// self.customArray.exchangeObjectAtIndex(indexPath.row, withObjectAtIndex: sourceIndexPath.row)
//move the row
self.tableView.moveRowAtIndexPath(sourceIndexPath, toIndexPath: indexPath)
//and update source so it is in sync with UI changes
sourceIndexPath = indexPath
}
if (location.y < 68) || (location.y > 450) {
print("cancelled")
self.snapshot.alpha = 0.0
cell.hidden = false
UIView.animateWithDuration(0.10, animations: { () -> Void in
self.snapshot.center = cell.center
self.snapshot.transform = CGAffineTransformIdentity
self.snapshot.alpha = 0.0
//undo fade out
cell.alpha = 1.0
}, completion: { (finished) in
self.snapshot.removeFromSuperview()
})
}
case UIGestureRecognizerState.Ended:
//clean up
print("ended")
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cell.hidden = false
UIView.animateWithDuration(0.25, animations: { () -> Void in
self.snapshot.center = cell.center
self.snapshot.transform = CGAffineTransformIdentity
self.snapshot.alpha = 0.0
//undo fade out
cell.alpha = 1.0
}, completion: { (finished) in
self.snapshot.removeFromSuperview()
})
break
default:
break
}
}else{
gesture.cancelsTouchesInView = true
}
}
func customSnapshotFromView(inputView: UIView) -> UIView {
// Make an image from the input view.
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0)
inputView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
// Create an image view.
let snapshot = UIImageView(image: image)
snapshot.layer.masksToBounds = false
snapshot.layer.cornerRadius = 0.0
snapshot.layer.shadowOffset = CGSize(width: -5.0, height: 0.0)
snapshot.layer.shadowRadius = 5.0
snapshot.layer.shadowOpacity = 0.4
return snapshot
}
}
When I scroll after dragging it looks like:
As you can see cell is not appearing again. I want to drag and drop static cell and I want to save it's position so I will not rearrange again when I scroll.
Sample project for more Info.
This is just a demo project But I have added many elements into my cell and every cell have different UI.
There is a library that does exactly what you are looking to do with a very similar approach. It's called FMMoveTableView but it's for cells with a datasource.
I think that what is causing your problem is that when you move the cells around and then you scroll the datasource from the storyboard is no longer in sync with the table and therefore your cell object can't be redrawn.
I think you should implement your table this way:
Make your 4 cells custom cells.
Subclass each one.
Create an Array with numbers 1 to 4
Reorder the array on long drag
Override cellForRowAtIndexPath to show the right cell for the right number
You can drag uitableview cell from uitableview delegates .......
1) set the table view editing style to none in its delegate.
2) implement table view delegate to enable dragging of cell i.e canMoveRowAtIndexPath methods...
You can create multiple dynamic cells.
You'll just have to dequeue cells with correct identifier.
Are you doing this for layout purposes only, maybe a UICollectionView or a custom made UIScrollView could do the job?
Never the less, I have a solution:
Create a IBOutlet collection holding all your static UITableViewCells
Create a index list to simulate a "data source"
Override the cellForRowAtIndexPath to draw using your own index list
When updating the list order, update the indexList so that the view "remembers" this change
This Table view controller explains it all:
class TableViewController: UITableViewController {
#IBOutlet var outletCells: [UITableViewCell]!
var indexList = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
// Prepare a index list.
// We will move positions in this list instead
// of trying to move the view's postions.
for (index, _) in outletCells.enumerate() {
indexList.append(index)
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Use dynamic count, not needed I guess but
// feels better this way.
return outletCells.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Use the index path to get the true index and return
// the view on that index in our IBOutlet collection
let realIndexForPos = indexList[indexPath.row]
return outletCells[realIndexForPos]
}
#IBAction func onTap(sender: AnyObject) {
// Simulating your drag n drop stuff here... :)
let swapThis = 1
let swapThat = 2
tableView.moveRowAtIndexPath(NSIndexPath(forItem: swapThis, inSection: 0), toIndexPath: NSIndexPath(forItem: swapThat, inSection: 0))
// Update the indexList as this is the "data source"
// Do no use moveRowAtIndexPath as this is never triggred
// This one line works: swap(&indexList[swapThis], &indexList[swapThat])
// But bellow is easier to understand
let tmpVal = indexList[swapThis]
indexList[swapThis] = indexList[swapThat]
indexList[swapThat] = tmpVal
}
}
To create the IBOutlet use the Interface Builder.
Use the Referencing Outlet Collection on each Table View Cell and drag, for each, to the same #IBOutlet in your controller code.

UITableView cell selection animation and NSFetchedResultsController

I have a UITableViewController which allows multiple selection and it presents model of the data stored in the CoreData.
When user taps on the row we need to animate it to selected state (change layout, fade in some elements etc). The problem is that when user taps on row and makes it selected - model gets updated (because we store selected items in the model too). Because of the NSFetchedResultController aborts our fancy animations as whole table gets reloaded.
To make it more clear. Here is configureCell method i call in tableView:cellForRowAtindexPath. The reason to have setSelected method with animated: false there is to set already selected cells to a proper state when user scrolls the table.
func configureCell(cell: MealCell, indexPath: NSIndexPath) {
let menuItem = self.fetchedResultsController.objectAtIndexPath(indexPath) as! MenuItem
cell.name = menuItem.name
// some cell initialisation
//we have this code to draw cells selected when user scrolls our table view. We don't need animation here.
if menuItem.isSelected {
cell.setSelected(true, animated: false)
} else {
cell.setSelected(false, animated: false)
}
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
}
All animations happen in setSelected method of the MealCell class
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
extraView.hidden = !selected
let extraViewAlpha: CGFloat = selected ? 1.0 : 0.0
self.extraViewWidthConstraint.constant = selected ? 38 : 0
if animated {
UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
self.layoutIfNeeded()
}, completion: { completed -> Void in
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.extraView.alpha = extraViewAlpha
})
})
}
else {
self.layoutIfNeeded()
self.extraView.alpha = extraViewAlpha
}
}
The best is to change your way of storing selectedItems to prevent the fetchedResultsController from notifying the delegate of the changes. a workaround is to temporary set the fetchedResultsController.delegate to nil and set it back after animation ends.

How to get a reference to button from UITableViewRowAction?

I have a table view and I use UITableViewRowAction to present two options to the user when a swipe is done on a row, 'delete' and 'more', and I want the more to show a popover. The popover needs to know the view it is anchored at, so how do I get a reference to the button which says 'more'?
My code:
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
var moreRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "More", handler:{action, indexpath in
let p = SomeViewController(aClient: client)
let aPopover = UIPopoverController(contentViewController: p)
let popRect = self.view.frame
// What should x be?
let x: UIView = self.tableView.cellForRowAtIndexPath(indexPath)!
aPopover.presentPopoverFromRect(popRect, inView: x, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
});
var deleteRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete", handler:{action, indexpath in
// Do something to delete.
});
return [deleteRowAction, moreRowAction];
}
This code shows the popover as anchored at the whole cell, not at the 'more' button.
I have searched the documentation about UITableViewRowAction, but nothing there gave me a lead. The type of the action argument is also UITableViewRowAction so I'm not sure it can be used.
Update
According to the comment, I got it working. I call a method in the custom cell view controller, there I access the subviews array. I don't like accessing subviews array and assuming the position of the button in that array.
The working code:
In the tableview callback I do:
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
var moreRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Plus", handler:{action, indexpath in
let cell = self.tableView.cellForRowAtIndexPath(indexPath)! as MyCell
cell.showPopover()
});
Inside MyCell I do:
func showPopover() {
let p = ClientLongPressViewController(client: self.client)
let aPopover = UIPopoverController(contentViewController: p)
let x: UIView = self.subviews[0].subviews[1] as? UIView ?? self
let popRect = x.frame
aPopover.setPopoverContentSize(CGSize(width: 215, height: 241), animated: true)
aPopover.presentPopoverFromRect(popRect, inView: x, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
}
I tried (inside MyCell) to access self.contentView but this only contains elements from the main cell view, not the swiping elements.
Also tried using self.editingAccessoryView, but it is nil.
So I would still like a nicer way to get the "delete confirmation view", which I now access with self.subviews[0] (The second subview[1] is okay in my opinion because I decide it's position when I return [deleteRowAction, moreRowAction].
There doesn't seem to be any straight forward way access the button itself, but after you swipe the cell, I believe that what happens, is that the cell is moved to the left, and a new view called the UITableViewCellDeleteConfirmationView is added on the right. So, if you position the popover at the cell's origin plus its width, it will be at the right edge of the cell, which will be just to the left of your button (assuming that your button is the left most one if you have multiple buttons).
var moreRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "More", handler:{action, indexpath in
let p = SomeViewController(aClient: client)
let aPopover = UIPopoverController(contentViewController: p)
let cell: UITableViewCell = self.tableView.cellForRowAtIndexPath(indexPath)!
let popRect = CGRectMake(cell.frame.origin.x + cell.frame.size.width, cell.frame.size.height/2.0, 1, 1);
aPopover.presentPopoverFromRect(popRect, inView: cell, permittedArrowDirections: .Right, animated: true)
});

Resources