How can i get and detect the indexPath of Item Focused in collectionView with this code or ?
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{}
Use didUpdateFocusInContect - UICollectionViewDelegate
func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
// indexPath that is going to be focused
print(context.nextFocusedIndexPath)
// indexPath that is going to lose focus
print(context.previouslyFocusedIndexPath)
}
Related
I am trying to retrieve data from an online database, and I do that successfully; However, after retrieving the data from the database I would like to store it in an array and then populate a listview and a mapview with it's data, but there is a problem, I am able to load the data and store it and view it, however the problem is that everytime the app loads no information appears until I go to another scene and go back, because I am populating the array though the AppDelegate. However, if I populate it through the viewdidload I get duplicate items in my table view.
Here is my code:
Approach number 1, which leads to duplicates
StoreViewController.swift
class StoreViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate, StoresModelProtocoal {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
drawForm()
setUpMap()
self.hideKeyboardWhenTappedAround()
getCurrentLocation()
let hideStoreDetail: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.hideStoreDetails))
Map.addGestureRecognizer(hideStoreDetail)
//Create a nib for the custom cell and use it in the table
let nib = UINib(nibName: "CustomStoreCell", bundle: nil)
StoresListTable.registerNib(nib, forCellReuseIdentifier: "customStoreCell")
let storesModel = StoresModel()
storesModel.delegate = self
storesModel.downloadItems()
}
func itemsDownloaded(items: NSArray) {
print("Items downloaded")
for item in items
{
if let s = item as? Store
{
print(s.Address)
Globals.unsortedStoresList += [s]
Map.addAnnotation(s.Annotation)
do_table_refresh()
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Globals.unsortedStoresList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CustomStoreCell = self.StoresListTable.dequeueReusableCellWithIdentifier("customStoreCell") as! CustomStoreCell
let s = Globals.unsortedStoresList[indexPath.row]
cell.loadItem(s.Name, StoreAddress: s.Address, StoreHoursOfOperation: s.HoursOfOperation, StoreDistanceFromCurrentLocation: String(s.DistanceFromCurrentLocation))
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//tableView.deselectRowAtIndexPath(indexPath, animated: true)
let s = Globals.unsortedStoresList[indexPath.row]
print(s.Name)
print(s.Address)
print(s.HoursOfOperation)
print(s.DistanceFromCurrentLocation)
//print("You selected cell #\(indexPath.row)!")
}
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.StoresListTable.reloadData()
return
})
}
I know this one duplicates the items because everytime the view is loaded it re-downloads all the data again; therefore, I tried looking for a better way and then I thought about doing the downloading process in my AppDelegate and then just write couple functions that take data from the array and display it, but the problem here is that the data would be displayed on the TableView right away without duplicates but it won't be displayed on the mapview at first run, instead I have to go to another scene and go back in order for the data to be displayed on the map.
Approach number 2
StoreViewController.swift
class StoreViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
drawForm()
setUpMap()
self.hideKeyboardWhenTappedAround()
getCurrentLocation()
let hideStoreDetail: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.hideStoreDetails))
Map.addGestureRecognizer(hideStoreDetail)
//Create a nib for the custom cell and use it in the table
let nib = UINib(nibName: "CustomStoreCell", bundle: nil)
StoresListTable.registerNib(nib, forCellReuseIdentifier: "customStoreCell")
loadMapAnnotations()
}
func loadMapAnnotations(){
for item in Globals.unsortedStoresList
{
Map.addAnnotation(item.Annotation)
do_table_refresh()
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Globals.unsortedStoresList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CustomStoreCell = self.StoresListTable.dequeueReusableCellWithIdentifier("customStoreCell") as! CustomStoreCell
let s = Globals.unsortedStoresList[indexPath.row]
cell.loadItem(s.Name, StoreAddress: s.Address, StoreHoursOfOperation: s.HoursOfOperation, StoreDistanceFromCurrentLocation: String(s.DistanceFromCurrentLocation))
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//tableView.deselectRowAtIndexPath(indexPath, animated: true)
let s = Globals.unsortedStoresList[indexPath.row]
print(s.Name)
print(s.Address)
print(s.HoursOfOperation)
print(s.DistanceFromCurrentLocation)
//print("You selected cell #\(indexPath.row)!")
}
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.StoresListTable.reloadData()
return
})
}
AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, StoresModelProtocoal {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let storesModel = StoresModel()
storesModel.delegate = self
storesModel.downloadItems()
return true
}
//////////////////////////////////////
//Delegates
//////////////////////////////////////
func itemsDownloaded(items: NSArray) {
print("Items downloaded")
for item in items
{
if let s = item as? Store
{
print(s.Address)
Globals.unsortedStoresList += [s]
//Map.addAnnotation(s.Annotation)
}
}
}
Any help would be appreciated, Thanks in advance.
I was able to find a temporarily solution to the problem
I modified StoreViewController.swift to this, if anyone is having a similar problem.
class StoreViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate, StoresModelProtocoal {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
drawForm()
setUpMap()
self.hideKeyboardWhenTappedAround()
getCurrentLocation()
let hideStoreDetail: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.hideStoreDetails))
Map.addGestureRecognizer(hideStoreDetail)
//Create a nib for the custom cell and use it in the table
let nib = UINib(nibName: "CustomStoreCell", bundle: nil)
StoresListTable.registerNib(nib, forCellReuseIdentifier: "customStoreCell")
Globals.unsortedStoresList.removeAll() //I added this line of code to remove the old list
let storesModel = StoresModel()
storesModel.delegate = self
storesModel.downloadItems()
}
func itemsDownloaded(items: NSArray) {
print("Items downloaded")
for item in items
{
if let s = item as? Store
{
print(s.Address)
Globals.unsortedStoresList += [s]
Map.addAnnotation(s.Annotation)
}
}
do_table_refresh()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Globals.unsortedStoresList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CustomStoreCell = self.StoresListTable.dequeueReusableCellWithIdentifier("customStoreCell") as! CustomStoreCell
let s = Globals.unsortedStoresList[indexPath.row]
cell.loadItem(s.Name, StoreAddress: s.Address, StoreHoursOfOperation: s.HoursOfOperation, StoreDistanceFromCurrentLocation: String(s.DistanceFromCurrentLocation))
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//tableView.deselectRowAtIndexPath(indexPath, animated: true)
let s = Globals.unsortedStoresList[indexPath.row]
print(s.Name)
print(s.Address)
print(s.HoursOfOperation)
print(s.DistanceFromCurrentLocation)
//print("You selected cell #\(indexPath.row)!")
}
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.StoresListTable.reloadData()
return
})
}
I have added custom menu controller when long press on UICollectionViewCell
[self becomeFirstResponder];
UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:#"Custom Action"
action:#selector(customAction:)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:menuItem]];
[[UIMenuController sharedMenuController] setTargetRect: self.frame inView:self.superview];
[[UIMenuController sharedMenuController] setMenuVisible:YES animated: YES];
canBecomeFirstResponder Is also being called
- (BOOL)canBecomeFirstResponder {
// NOTE: This menu item will not show if this is not YES!
return YES;
}
//This method is not being called
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
NSLog(#"canPerformAction");
// The selector(s) should match your UIMenuItem selector
if (action == #selector(customAction:)) {
return YES;
}
return NO;
}
I have Also Implemented these methods
- (BOOL)collectionView:(UICollectionView *)collectionView
canPerformAction:(SEL)action
forItemAtIndexPath:(NSIndexPath *)indexPath
withSender:(id)sender {
if([NSStringFromSelector(action) isEqualToString:#"customAction:"]){
NSLog(#"indexpath : %#",indexPath);
UIAlertView *alertview = [[UIAlertView alloc] initWithTitle:#"warning.." message:#"Do you really want to delete this photo?" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alertview show];
return YES;
}
return YES;
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
- (void)collectionView:(UICollectionView *)collectionView
performAction:(SEL)action
forItemAtIndexPath:(NSIndexPath *)indexPath
withSender:(id)sender {
NSLog(#"performAction");
}
Though it is showing only "cut, copy, and paste" menus
Maybe a bit late but i maybe found a better solution for those who are still search for this:
In viewDidLoad of your UICollectionViewController add your item:
UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:#"Title" action:#selector(action:)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:menuItem]];
Add the following delegate methods:
//This method is called instead of canPerformAction for each action (copy, cut and paste too)
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == #selector(action:)) {
return YES;
}
return NO;
}
//Yes for showing menu in general
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
Subclass UICollectionViewCell if you didn't already. Add the method you specified for your item:
- (void)action:(UIMenuController*)menuController {
}
This way you don't need any becomeFirstResponder or other methods. You have all actions in one place and you can easily handle different cells if you call a general method with the cell itself as a parameter.
Edit: Somehow the uicollectionview needs the existence of this method (this method isn't called for your custom action, i think the uicollectionview just checks for existance)
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
}
You need to trigger delegate functions from custom UICollectionViewCell
Here is my working sample code for Swift3
CollectionViewController
override func viewDidLoad() {
super.viewDidLoad()
let editMenuItem = UIMenuItem(title: "Edit", action: NSSelectorFromString("editCollection"))
let deleteMenuItem = UIMenuItem(title: "Delete", action: NSSelectorFromString("deleteCollection"))
UIMenuController.shared.menuItems = [editMenuItem, deleteMenuItem]
}
override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
return true
}
override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return action == NSSelectorFromString("editCollection") || action == NSSelectorFromString("deleteCollection")
}
override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
print("action:\(action.description)")
//Custom actions here..
}
Add following functions to your custom UICollectionViewCell
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == NSSelectorFromString("editCollection") || action == NSSelectorFromString("deleteCollection")
}
To call delegate function from cell (needs to be in your custom UICollectionViewCell)
func editCollection()
{
let collectionView = self.superview as! UICollectionView
let d:UICollectionViewDelegate = collectionView.delegate!
d.collectionView!(collectionView, performAction: NSSelectorFromString("editCollection"), forItemAt: collectionView.indexPath(for: self)!, withSender: self)
}
func deleteCollection()
{
let collectionView = self.superview as! UICollectionView
let d:UICollectionViewDelegate = collectionView.delegate!
d.collectionView!(collectionView, performAction: NSSelectorFromString("deleteCollection"), forItemAt: collectionView.indexPath(for: self)!, withSender: self)
}
I've just spent two days trying to figure out the "correct" way of doing this, and barking up the wrong tree with some of the suggestions that are around.
This article shows the correct way of doing this. I hope that by posting it here someone will be saved a few hours.
http://dev.glide.me/2013/05/custom-item-in-uimenucontroller-of.html
Swift 3 Solution:
Simply do all stuff inside UICollectionView class and assign this class to UICollectionView object.
import UIKit
class MyAppCollectionView: UICollectionView {
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addLongPressGesture()
}
func addLongPressGesture() {
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(MyAppCollectionView.longPressed(_:)))
longPressGesture.minimumPressDuration = 0.5
self.addGestureRecognizer(longPressGesture)
}
func longPressed(_ gesture: UILongPressGestureRecognizer) {
let point = gesture.location(in: self)
let indexPath = self.indexPathForItem(at: point)
if indexPath != nil {
MyAppViewController.cellIndex = indexPath!.row
let editMenu = UIMenuController.shared
becomeFirstResponder()
let custom1Item = UIMenuItem(title: "Custom1", action: #selector(MyAppViewController.custome1Method))
let custom2Item = UIMenuItem(title: "Custom2", action: #selector(MyAppViewController.custome2Method))
editMenu.menuItems = [custom1Item, custom2Item]
editMenu.setTargetRect(CGRect(x: point.x, y: point.y, width: 20, height: 20), in: self)
editMenu.setMenuVisible(true, animated: true)
}
}
override var canBecomeFirstResponder: Bool {
return true
}
}
class MyAppViewController: UIViewController {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
// You need to only return true for the actions you want, otherwise you get the whole range of
// iOS actions. You can see this by just removing the if statement here.
//For folder edit
if action == #selector(MyAppViewController.custome1Method) {
return true
}
if action == #selector(MyAppViewController.custome2Method) {
return true
}
return false
}
}
When people have trouble getting menus to work on long press in a collection view (or table view, for that matter), it is always for one of two reasons:
You're using the long press gesture recognizer for something. You cannot, for example, have both dragging and menus in the same collection view.
You've forgotten to implement the selector in the cell.
For example, the OP's code says:
UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:#"Custom Action"
action:#selector(customAction:)];
The implication is that customAction is a method this class. This is wrong. customAction: must be a method of the cell class. The reason is that the runtime will look at the cell class and will not show the menu item unless the cell implements the menu item's action method.
For a complete minimal working example (in Swift), see my answer here: https://stackoverflow.com/a/51898182/341994
On iOS 9 with Swift to SHOW ONLY CUSTOM ITEMS (without the default cut, paste and so on), I only managed to make work with the following code.
On method viewDidLoad:
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(contextMenuHandler))
longPressRecognizer.minimumPressDuration = 0.3
longPressRecognizer.delaysTouchesBegan = true
self.collectionView?.addGestureRecognizer(longPressRecognizer)
Override method canBecomeFirstResponder:
override func canBecomeFirstResponder() -> Bool {
return true
}
Override these two collection related methods:
override func collectionView(collectionView: UICollectionView, shouldShowMenuForItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func collectionView(collectionView: UICollectionView, canPerformAction action: Selector,
forItemAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) -> Bool {
return (action == #selector(send) || action == #selector(delete))
}
Create the gesture handler method:
func contextMenuHandler(gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.Began {
let indexPath = self.collectionView?.indexPathForItemAtPoint(gesture.locationInView(self.collectionView))
if indexPath != nil {
self.selectedIndexPath = indexPath!
let cell = self.collectionView?.cellForItemAtIndexPath(self.selectedIndexPath)
let menu = UIMenuController.sharedMenuController()
let sendMenuItem = UIMenuItem(title: "Send", action: #selector(send))
let deleteMenuItem = UIMenuItem(title: "Delete", action: #selector(delete))
menu.setTargetRect(CGRectMake(0, 5, 60, 80), inView: (cell?.contentView)!)
menu.menuItems = [sendMenuItem, deleteMenuItem]
menu.setMenuVisible(true, animated: true)
}
}
}
And, finally, create the selector's methods:
func send() {
print("Send performed!")
}
func delete() {
print("Delete performed!")
}
Hope that helps. :)
Cheers.
I have a UITableView with edit mode enabled in my -viewDidLoad method. My problem is that I don't need the three-line 'move' symbol on the right of each cell.
[tableview setEditing:YES];
tableview.allowsSelectionDuringEditing=YES;
I need to either have a transparent disclosure button, or hide the disclosure button altogether. I have tried:
cell.editingAccessoryType = UITableViewCellAccessoryNone;
But this has no effect. Can anybody help me with this?
You can do that in your cell subclass using the below method. I don't think it will work as-is , you need to amend it. But this should be your lead.
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
//check whether you need it.
if ((state & UITableViewCellStateShowingEditControlMask) == UITableViewCellStateDefaultMask) {
for (UIView *subview in self.subviews) {
//check for reorder control. it checks for delete button.
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationControl"]) {
subview.hidden = YES;
subview.alpha = 0.0;
}
}
}
}
Modify tableView(_:canMoveRowAt) and return false, this is an UITableViewDataSource method.
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return false
}
I have a tableView with 10 sections and each section has 3 rows.
Is it possible to reoder the section using the tableViewDelegate methods
-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath {
}
Right now if i am dragging a row from one section and dropping it on another section, its being added to that section. The footer view is also not being selected for reordering, and it remains in the original section.
Any ideas?
Thanks
Since this comes up as one of the first results in Google I thought I'd add the swift code. The Objective-C will is almost the same except for the name of the indexPaths in moveRowAtIndex.
Enable editing mode:
If you're using a UITableViewController you toggle editing mode with:
func toggleEditing() {
self.editing = !self.editing
}
If you're not using a UITableViewController (e.g. you have added a table view to a UIViewController (or any View Controller other than UITableViewController) then you toggle it with:
func toggleEditing() {
self.editing = !self.editing
}
Note: tableView is the name of your UITableView.
Implement canMoveRowAtIndexPath
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
Implement moveRowAtIndexPath
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
var itemToMove = tableData[fromIndexPath.row]
tableData.removeAtIndex(fromIndexPath.row)
tableData.insert(itemToMove, atIndex: toIndexPath.row)
}
Check out this tutorial: Add, Delete & Reorder UITableView Rows. There's a working project at the end of the tutorial.
I have got an UITableView. How to simply fill it with three elements, for example "e1", "e2", "e3" ?
set DataSource of your table to your class and define in your class 3 methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = #"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellId];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellId] autorelease];
}
[cell setText:[NSString stringWithFormat:#"e%i",indexPath:[indexPath row]];
return cell;
}
UITableView is a component that rewards study. Read the local doc pages on UITableView and its datasource and delegate protocols.
There is a table view programming guide in the docs as well. It is a good read.
Some of it took me a while to grok and I am still refining my approach each time I implement one.
Using the API and implementing the methods it requires, you should be able to bend it to your will.
Hellra1ser's example should push you in the right direction. Lookup those methods first, you will be using them alot.
Code in swift 3.1:
Just in case if someone needs to copy paste it for the purpose of testing.
extension ViewController : UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Test"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}