Drag from a collection view cell works on iPad simulator but not on iPhone simulator or real device in Swift 4 - drag-and-drop

I am using the new drag and drop api in swift 4. I need to drag a cell by long tapping on it and dropping the image on the view controller's view. This is dragging and dropping in the same application because it needs to work on the iPhone, and this feature only works on the phone in the same app. I use a real device for testing what I write and I could not figure out why I cannot drag a cell once I have tapped on it. But running the simulator on iPad and ...it works, I can drag it. I have gone over apple's documentation and they specify that the drag item's appearance can be configured during the drag, but the default is that the whole cell will be dragged if no custom implementation of the delegate's function is present or nil is returned; so it should still work, it does it in the simulator on iPads but not on simulated iPhones or the real one. Code listing below. Maybe someone has gone over this already and knows how to help me. Thank you.
Only the relevant bits:
class DefineRoomPropertiesVC: UIViewController {
//MARK: - Init.
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupTopMenuBar()
setupViews()
topMenuCollectionView.dragDelegate = self
}
fileprivate let topMenuCollectionView: TopMenuBarCollectionView = {
let layout = UICollectionViewFlowLayout()
let collectionView = TopMenuBarCollectionView(frame: .zero, collectionViewLayout: layout)
return collectionView
}()
}
extension DefineRoomPropertiesVC: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { // called when a drag is initiated.
let image = topMenuCollectionView.imagesForTopSectionCell[indexPath.item]
let itemProvider = NSItemProvider(object: image)
let dragItem = UIDragItem(itemProvider: itemProvider)
return [dragItem]
}
func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters? {
return nil
}

Maybe you forgot to implement some delegate's methods. Here is the great tutorial: Coding for iOS 11: How to drag & drop into collections & tables
Make sure you set dragInteractionEnabled flag to true
collectionView.dragInteractionEnabled = true

Pretty stupid, but Apple has enabled the drag interaction for iPad but set it to false on iPhone as a default value.
You would be adding your UIDragInteraction to your UIView. Make sure that it is enabled by setting the value to true. In my code, it looks as follows.
let dragInteraction = UIDragInteraction(delegate: self)
dragInteraction.isEnabled = true
dragView.addInteraction(dragInteraction)

Related

Safari app extension popover not calling table view notification methods

I want to include a view-based NSTableView in the popover of a Safari App Extension.
Starting with the default project in Xcode, I made the SFSafariExtensionViewController the delegate and datasource for the table view as it is the only content on the popover, and mostly this works.
I can populate the table and implement methods like tableView(_:shouldSelectRow:), yet methods which return a notification object such as tableViewSelectionDidChange(_:) do not get called.
Whilst those methods show a cludgy way of knowing when a row is selected, I am left with no way of knowing when a cell is edited.
As I had to connect the delegate outlet of the NSTableView to the File Owner to allow the delegated methods to work, I also tried connecting the dataSource outlet too, but this rightly did not help.
Here is the essence of my code (which for now includes returning dummy table data to test editing):
class SafariExtensionViewController: SFSafariExtensionViewController {
#IBOutlet weak var tableView: NSTableView!
static let shared: SafariExtensionViewController = {
let shared = SafariExtensionViewController()
shared.preferredContentSize = NSSize(width:445, height:421)
return shared
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func textDidEndEditing(_ notification: Notification) {
NSLog("I will NEVER appear in the console")
}
}
extension SafariExtensionViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return 5
}
}
extension SafariExtensionViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cellView = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? NSTableCellView
cellView?.textField?.stringValue = "Blah"
return cellView
}
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
NSLog("I will appear in the console")
return true
}
func tableViewSelectionDidChange(_ notification: Notification) {
NSLog("I will NEVER appear in the console")
}
func controlTextDidEndEditing(_ obj: Notification) {
NSLog("I will NEVER appear in the console")
}
}
(Obviously I do not need both textDidEndEditing(_:) and controlTextDidEndEditing(_:) but I am just trying everything.)
I am guessing the problem is something to do with the table view not being registered for notifications within a SFSafariExtensionViewController? That object inherits from NSViewController, though, so I would have thought these methods should work automatically.
This is my first time using swift, and it is a long time since I wrote a Mac app. But the actual functionality of the extension works, now I just want to have the ability to customize the settings through the UI.
However there seems to be very little written about Safari app extension programming, Apple's documentation is sparse, and I have not even been able to find any code examples featuring a table view in a popover to learn from.
I am probably missing something very obvious, but I have run out of searches to try on here and the web in general, so any help will be appreciated.
UPDATE:
I think I have an answer, by explicitly linking the NSTextFields in the table to the File's Owner as a delegate, the tableViewSelectionDidChange(_:) and controlTextDidEndEditing(_:) methods are now working. There must have been something else wrong causing the former to not work that I accidentally broke and fix, but it makes some sense for the latter.
That is all I need for the functionality to work, however I am still confused why the textDidEndEditing(_:) is still not working when I am led believe it should.
And in Apple's documentation, textDidEndEditing(_ :) is a method of an NSTextField, which links to a page saying controlTextDidEndEditing(_ :) is deprecated
And I misunderstanding anything?
I think you are not setting up the outlet properly please confirm this. Also check you setting up reusable identifier? identifier. for me all delegate calling without no issue after that.

Collection View Cell Button not triggering action

So I have a button that looks like a pencil in a collection view cell xib.
Then I have this code.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "groupsCollectionViewCellIdentifier", for: indexPath) as! GroupCollectionViewCell
//add action to the edit button
cell.editButton.tag = indexPath.row
cell.editButton.addTarget(self, action: #selector(GroupsCollectionViewController.editGroupAction), for: .touchUpInside)
return cell
}
//segue to edit group
func editGroupAction() {
performSegue(withIdentifier: "editGroupSegue", sender: self)
}
But whenever I click the edit button. Nothing is happening. I wonder what's missing.
I spent hours scouring the web for a solution to my UIButton's inside UICollectionView's not working. Driving me nuts until I finally found a solution that works for me. And I believe it's also the proper way to go: hacking the hit tests. It's a solution that can go a lot deeper (pun intended) than fixing the UICollectionView Button issues as well, as it can help you get the click event to any button buried under other views that are blocking your events from getting through:
UIButton in cell in collection view not receiving touch up inside event
Since that SO answer was in Objective C, I followed the clues from there to find a swift solution:
http://khanlou.com/2018/09/hacking-hit-tests/
--
When I would disable user interaction on the cell, or any other variety of answers I tried, nothing worked.
The beauty of the solution I posted above is that you can leave your addTarget's and selector functions how you are used to doing them since they were most likey never the problem. You need only override one function to help the touch event make it to its destination.
Why the solution works:
For the first few hours I figured the gesture wasn't being registered properly with my addTarget calls. It turns out the targets were registering fine. The touch events were simply never reaching my buttons.
The reality seems to be from any number of SO posts and articles I read, that UICollectionView Cells were meant to house one action, not multiple for a variety of reasons. So you were only supposed to be using the built in selection actions. With that in mind, I believe the proper way around this limitation is not to hack UICollectionView to disable certain aspects of scrolling or user interaction. UICollectionView is only doing its job. The proper way is to hack the hit tests to intercept the tap before it gets to UICollectionView and figure out which items they were tapping on. Then you simply send a touch event to the button they were tapping on, and let your normal stuff do the work.
My final solution (from the khanlou.com article) is to put my addTarget declaration and my selector function wherever I like (in the cell class or the cellForItemAt override), and in the cell class overriding the hitTest function.
In my cell class I have:
#objc func didTapMyButton(sender:UIButton!) {
print("Tapped it!")
}
and
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard isUserInteractionEnabled else { return nil }
guard !isHidden else { return nil }
guard alpha >= 0.01 else { return nil }
guard self.point(inside: point, with: event) else { return nil }
// add one of these blocks for each button in our collection view cell we want to actually work
if self.myButton.point(inside: convert(point, to: myButton), with: event) {
return self.myButton
}
return super.hitTest(point, with: event)
}
And in my cell class init I have:
self.myButton.addTarget(self, action: #selector(didTapMyButton), for: .touchUpInside)
Try this one on your cell
cell.contentView.isUserInteractionEnabled = false
Considering your problem I have created a demo with the details you have provided above with some minor changes.
I have modified your cellForItem method. My modified version is as below.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : GroupCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "groupsCollectionViewCellIdentifier", for: indexPath) as! GroupCollectionViewCell
cell.editButton.tag = indexPath.row
cell.editButton.addTarget(self, action: #selector(editGroupAction(sender:)), for: .touchUpInside)
return cell
}
And the action method is written as below:
func editGroupAction(sender: UIButton) {
print("Button \(sender.tag) Clicked")
}
From your code I have modified below Details:
1) Way of declaration of UICollectionViewCell Object.
2) Way of Assigning #Selector Method to the UIButton. And at the last
3) In the action method I have print the Button tag.
With the above Changes I am getting proper result. I got the selected Index value as button tag, as assigned in code in consol. The output I am getting is as below.
Button 2 Clicked
Button 3 Clicked
Button 4 Clicked
I hope this will work for your requirement.
I was having the same issue and after using the View hierarchy, I have noticed that an additional UIView was added to the cell.
This view was above my UIButton, so touch event was not passed down to the button, the didSelectItem was triggered instead.
I solved this by using the following call:
cell.bringSubviewToFront(cell.button)
Now the event will be called.
To enable touch action on the UIButton of your Custom UICollectionCell, add the below method in your Custom UICollectionCell class.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var view = myButton.hitTest(myButton.convert(point, from: self), with: event)
if view == nil {
view = super.hitTest(point, with: event)
}
return view
}
If you have to cover a number of buttons, here's a way to write the same code:
class SomeCell: UICollectionViewCell {
#IBOutlet var drawButton: UIButton?
#IBOutlet var buyButton: UIButton?
#IBOutlet var likeButton: UIButton?
#IBOutlet var sayButton: UIButton?
// fix crazy Apple bug in collection view cells:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let drawButton = drawButton, let v = drawButton.hitTest(
drawButton.convert(point, from: self), with: event) {
return v
}
if let buyButton = buyButton, let v = buyButton.hitTest(
buyButton.convert(point, from: self), with: event) {
return v
}
if let likeButton = likeButton, let v = likeButton.hitTest(
likeButton.convert(point, from: self), with: event) {
return v
}
// etc ...
return super.hitTest(point, with: event)
}
}
I realize this ticket is pretty old, but I had the same problem, and found a different solution.
I ran Xcode with the view-debugger. While the button was sized correctly, it's superview had a width of 0 points. This would make it impossible to tap on this button, or any other subviews.
Just mentioning this, as it might be worth trying out.
If you declare your buttons programmatically you need to make sure they are set as lazy var objects to ensure self is fully defined first, before the button is initialised.
class myCell: UICollectionViewCell {
static let identifier = "myCellId"
lazy var myButton: UIButton = {
let btn = UIButton()
btn.addTarget(self, action: #selector(handleButtonPress(_:)), for .touchUpInside)
return btn
}()
// rest of cell
}
Are you assign the class to the cell and link the button with controller and also assign the identifier to the segue and also use break point to check if the func is call or not
and use segue like this
func editGroupAction() {
performSegue(withIdentifier: "editGroupSegue", sender: self)
let src = self.sourceViewController as UIViewController
let dst = self.destinationViewController as UIViewController
src.navigationController.pushViewController(dst, animated:false)
}
If you are using iOS 10.Following is working code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! YourCellClass
cell.btnJoin.tag = (indexPath as NSIndexPath).row
cell.btnJoin.addTarget(self, action: #selector(YourViewControllerClassName.doSomething(_:)), for: UIControlEvents.touchUpInside)
}
Action
func doSomething(_ sender: UIButton) {
print("sender index",sender.tag)
}
By the way, I had to implement the hit test solution then I had this issue below:
I had a similar problem where my one of my buttons was working while another one towards the bottom of the cell was not working and it was inside a stack view. So after many hours of looking around, I found that one of the parent views (between the button and the CollectionView itself) was having a size less than the size of its subviews which in this case the touches were not reported at all since they were happening outside of the parent view... Fixed that and it's working like a charm...
To be more specific I used a compositional layout for my collection view and the view called "_UICollectionViewOrthogonalScrollerEmbeddedScrollView" which is a wrapper for the collectionView cells and the estimated size was smaller than the cell it self

Does the keyboard for a UITextField have to take up the whole screen?

It seems that the keyboard doesn't have to take up the whole screen, check UPDATE in the Question section of my post. Thanks.
Description
Use UITextField to place a full-screen keyboard on the screen.
reference
I've set up a UISplitViewController and I would like the RootViewController (aka MasterViewController) to have the UITextField with the Keyboard showing. Then I would like results of the search on the right (in the "ResultViewController" (UIViewController).
The idea is when the user types, results are proposed.
What I've tried:
I first added a UITextField to my RootViewController via the storyboard but that took up the whole screen when I activated the keyboard via textField.becomeFirstResponder().
I figured if I use a UIAlertController I'd get by this issue, but the keyboard still takes up the whole screen.
class RootViewController: UIViewController {
override func viewDidLoad() {
let alertController = UIAlertController(title: "Search", message: "Search for something!.", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
textField.placeholder = "Search"
})
self.addChildViewController(alertController)
alertController.view.frame = self.view.frame
self.view.addSubview(alertController.view)
}
}
Question:
When using a UISplitViewController how can I get the keyboard to only stay in it's RootViewController and not take up the whole screen?
UPDATE: It appears this has been implemented in the Netflix app on the new apple tv. The keyboard is on the top and takes up the whole width. The bottom is divided into two sections. On the left, proposed word results and on the right a collection view of video covers of possible videos. Everything is updated as the user types.
If this is considered bad design for Apple TV, then feel free to point out why.
Screen shot:
This is what I currently get. The text box opens a keyboard that takes up the whole screen.
Presenting a keyboard only on a portion of the screen is not possible with the built-in SDK methods.
A UIKeyboard is presented on the application's window, not on any of its subviews. To accomplish your desired behavior, use reflection to get a reference to the UIKeyboard object on your application's UIWindow (by iterating through the window's subviews), and change its frame to match the width of your RootViewController.
To get started, you can look at the private UIKeyboard.h tvOS header here. I should note that Apple may have code in place to disable resizing of the keyboard (like in willMoveToWindow: or didMoveToWindow for example). This is undefined behavior and your milage will vary, if it even works at all.
You have deleted your comment on my answer about the Netflix app, but as shirefriendship says in their answer, Netflix probably uses a TVML template. You could go that route and build a hybrid tvOS/TVML app. Additionally, you could also manually build a keyboard yourself with a UICollectionView in your RootViewController.
Netflix is using TVML Templates to implement their search instead of UIKit. You can accomplish the same aesthetic as Netflix using the searchTemplate. You can learn to mix TVML and UIKit here. Unfortunately the searchTemplate is not customizable enough to conform to your desired layout. There is currently no way to implement your specific layout for the Apple TV.
Introduction:
I was finally able to implement this without having to use TVML templates. The final solution looks something like this:
The general idea is to create a UICollectionViewController with a UICollectionViewCell. Then to programmatically add a keyboard and add it to your TabViewController via your AppDelegate.
How to implement this search view with results:
Step 1: Storyboard and Controller creation
Open your storyboard and create a UICollectionViewController (with a custom class "SearchResultViewController") that is not attached to your TabViewController.
Within it create a your UICollectionViewCell with whatever labels and images you want. UICollectionViewCell should have a custom class called "VideoSearchCell".
Your SearchViewController should have nothing else inside.
Step 2: Adding SearchViewController to TabViewController and implementing keyboard via AppDelegate programmatically
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
override init() {
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if let tabController = window?.rootViewController as? UITabBarController {
tabController.viewControllers?.append(configueSearchController())
}
return true
}
//... standard code in-between
func configueSearchController() -> UIViewController {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let searchResultController = storyboard.instantiateViewControllerWithIdentifier(SearchResultViewController.storyboardIdentifier) as? SearchResultViewController else {
fatalError("Unable to instatiate a SearchResultViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. Gastric Bypass)", comment: "")
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer = UISearchContainerViewController(searchController: searchController)
searchContainer.title = NSLocalizedString("Search", comment: "")
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
return searchNavigationController
}
}
Once you've added the basic skeleton of your SearchResultViewController you should be able to see the keyboard on the top of the Search view when you run your project.
Step 3: Handling text input and updating results
You'll notice that in my filterString I use a class called ScoreVideo and a StringSearchService. These are just classes I use to filter my Video list (aka: self.vms.videos).
So in the end, just take the filterString, create a new filtered list and reload your collection view.
import UIKit
import Foundation
class SearchResultViewController: UICollectionViewController, UISearchResultsUpdating {
//private let cellComposer = DataItemCellComposer()
private var vms: VideoManagerService!
private var filteredVideos = [ScoreVideo]()
static let storyboardIdentifier = "SearchResultViewController"
var filterString = "" {
didSet {
// Return if the filter string hasn't changed.
guard filterString != oldValue else { return }
// Apply the filter or show all items if the filter string is empty.
if self.filterString.isEmpty {
self.filteredVideos = StringSearchService.start(self.filterString, videos: self.vms.videos)
}
else {
self.filteredVideos = StringSearchService.start(self.filterString, videos: self.vms.videos)
}
self.collectionView?.reloadData()
}
}
override func viewDidLoad() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
self.vms = appDelegate.getVideoManagerService()
}
// MARK: UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("count..\(filteredVideos.count)")
return filteredVideos.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// Dequeue a cell from the collection view.
return collectionView.dequeueReusableCellWithReuseIdentifier(VideoSearchCell.reuseIdentifier, forIndexPath: indexPath)
}
// MARK: UICollectionViewDelegate
override func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
guard let cell = cell as? VideoSearchCell else { fatalError("Expected to display a `VideoSearchCell`.") }
let item = filteredVideos[indexPath.row]
cell.configureCell(item.video)
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
dismissViewControllerAnimated(true, completion: nil)
}
// MARK: UISearchResultsUpdating
func updateSearchResultsForSearchController(searchController: UISearchController) {
print("updating... \(searchController.searchBar.text)")
filterString = searchController.searchBar.text!.lowercaseString ?? ""
}
}
If something is unclear, feel free to ask some questions. I most likely forgot something. Thanks.
Answer inspired from apple sample code

NSCollectionView dataSource not working properly

I'm experimenting with the new NSCollectionView API, introduced in El Capitan.
Following the WWDC video, I create a subclass of NSCollectionViewFlowLayout to determine the layout of the collection view.
class Layout : NSCollectionViewFlowLayout {
override func prepareLayout() {
super.prepareLayout()
self.minimumInteritemSpacing = 0
self.minimumLineSpacing = 0
let cheight = self.collectionView!.bounds.height
let cwidth = self.collectionView!.bounds.width
self.itemSize = CGSizeMake(cwidth / 2.0, cheight / 6.0)
}
}
After that, I've created a NSObject subclass to serve as data source.
class DataSource : NSObject, NSCollectionViewDataSource {
func collectionView(collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(collectionView: NSCollectionView, itemForRepresentedObjectAtIndexPath indexPath: NSIndexPath) -> NSCollectionViewItem {
/* DOESN'T GET CALLED! */
let item = collectionView.makeItemWithIdentifier("cell", forIndexPath: indexPath)
item.view.wantsLayer = true
item.view.layer?.backgroundColor = NSColor.redColor().CGColor
return item
}
}
The issue is that collectionView:itemForRepresentedObjectAtIndexPath: never gets called.
This is how I initialise the collection view:
let collectionView = NSCollectionView(frame: view.bounds)
let dataSource = DataSource()
let layout = Layout()
collectionView.collectionViewLayout = layout
collectionView.registerClass(NSCollectionViewItem.self,
forItemWithIdentifier: "cell")
collectionView.dataSource = dataSource
collectionView.backgroundColors = [.blackColor()]
I can clearly see the collection view in its superview, but there are no cells.
Also, this line, if called outside the delegate (but after the cell class is registered) makes the app crash!
let item = collectionView.makeItemWithIdentifier("cell", forIndexPath: /*any index path*/)
Am I doing something wrong or is NSCollectionView new API broken?
Thanks.
The method makeItemWithIdentifier is crucial and looks for .xib named "cell" in you bundle: you may need register it with registerNib:forItemWithIdentifier: if it comes from another bundle.
Thus your problem comes before that, since your collectionView:itemForRepresentedObjectAtIndexPath never gets called and this could arise from your pattern: why do you create a new class to serve as datasource and not use the same NSViewController where the NSCollectionView resides?
I always do so and with the new API all works perfectly. Hope this could help you!
I had almost the same problem, and here is how I solved it.
First of all the problem I had :
when my collection view delegate was NOT implementing the NSCollectionViewDelegateFlowLayout protocol, everything was fine and the collectionView:itemForRepresentedObjectAtIndexPath method was called
when my collection view delegate was implementing the NSCollectionViewDelegateFlowLayout protocol, collectionView:itemForRepresentedObjectAtIndexPath method was NOT called
I commented out every NSCollectionViewDelegateFlowLayout method in the delegate, and it turned out that if the method collectionView:layout:referenceSizeForHeaderInSection: was commented out, then everything was fine again (though I don't use headers).
It turned out that collectionView:layout:referenceSizeForHeaderInSection: was returning a NaN instead of 0.0 as the width in the size (stupid copy/paste mistake).
Are you sure you don't have the same kind of problem in what your layout is returning ?
The issue I was having was that I didn't register either nib or class for the items of the collection view. The symptoms were exactly like those you describe -- numberOfItemsInSection was called, and itemForRepresentedObjectAtIndexPath was not. After registering a nib, the method started being invoked. Here's the relevant answer -- https://stackoverflow.com/a/39516035/4635852

how to create xib subview at the center of tableview swift

I have tableview and I created the custom xib uiview as "detailview" for it. I want to show this detailview at the center of scrolled area when tapped to tableview cell. I can show this view but cannot centralized it. when I set value to frame manually, subview will be at center (approximately) but when I tap the cell which is at the bottom, the subview is appearing at the top of page and also it is moving when i scroll the tableview.
Please help me to show this view at the center of the scrolled area and be fixed
Here is my codes;
Detail View :
class TopTenDetailView: UIView {
var screenWidth:CGFloat = UIScreen.mainScreen().bounds.width*0.08
var screenHeight :CGFloat = UIScreen.mainScreen().bounds.height*0.08
class func instanceFromNib() -> UIView {
return UINib(nibName: "TopTenDetail", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius=10
let testFrame : CGRect = CGRectMake(screenWidth,screenHeight,320,480)
self.frame = testFrame
self.userInteractionEnabled=true
}
#IBAction func close(sender: UIButton) {
self.hidden=true
}
}
And TableViewController's method ;
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var detailView = TopTenDetailView.instanceFromNib
self.view.addSubview(detailView())
}
The way this is setup has many problems and i would be surprised if it actually ever works as intended.
A much better, simpler setup uses a OverFullScreen presentation style and it goes like this:
Create a separate UIViewController for your detail view, let's call it DetailViewController use Interface Builder. Make sure to set the background color to CLEAR
Wire up a segue from the "base" UIViewController that holds your UITableView to DetailViewController and give the segue a name. Let's call it 'detailSegue' , basically drag from one view controller to the other. Make sure that you are not dragging from the view but from the yellow icon at the top of the view controller. You are done in Interface Builder.
Ok, now for the code:
// MARK : - UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("detailSegue", sender: self)
}
// MARK: - segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? UIViewController{
vc.modalPresentationStyle = .OverFullScreen
}
}
The OverFullScreen presentation style uses a proper UIViewController modal segue but leaves the presenting UIViewController visible under the presented one.
You can then just layout whatever you want on DetailViewController using Interface Builder and autolayout without having to do hacky match calculations on the layout at runtime.
Hope it helps!