I would like to create an item store similar to a game like diablo, that you can drag and drop items from one UIView to another. In my scenario I used a table to hold the items and added an event to the cells for drag and drop.
It works but my issue is that i can't move the object out of the visible space from the table. As soon as I move the item farther from the border of the table the UIView became invisible.
I assume that I have to create a copy of the table cell and attach it to the superview. But as soon as I do that I loose the control of the UIView and cannot longer drag.
I looked at this example: https://github.com/mmick66/KDDragAndDropCollectionView which addresses my needs but it is way to complicated and contains some bugs.
I wonder if there is another way to easily attach the UIView of a cell to the ViewController and allow to drag it around ?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("itemrow", forIndexPath: indexPath)
cell.addTarget(self, action: "move:event:", forControlEvents: UIControlEvents.TouchDragInside)
return cell}
func move(btn : UIButton, event :UIEvent)
{
let touch = event.touchesForView(btn)!.first
let previousLocation : CGPoint = touch! .previousLocationInView(btn)
let location : CGPoint = touch! .locationInView(btn)
let delta_x :CGFloat = location.x - previousLocation.x
let delta_y :CGFloat = location.y - previousLocation.y
btn.center = CGPointMake(btn.center.x + delta_x,
btn.center.y + delta_y);
if chk(btn, target: basket){
print “yu bought it”
}
}
If you're okay with targeting iOS9 UICollectionView now has 'free' drag to rearrange functionality.
This post has an introduction to the methods that you'll need to implement: http://nshint.io/blog/2015/07/16/uicollectionviews-now-have-easy-reordering/
It's using a basic flow layout with a single 'section'. I'm not 100% clear the layout you're wanting to create, but potentially it sounds like it could also be a flow layout but with 2 or more separate sections to represent the areas you want to drag and drop objects into.
There are a few steps involved, in terms of support and flexibility, and stability it's the option I'd choose!
Related
how are you
I need to know how can i determine table view cell fully visible for playing auto play movie in cell and also detect hiding the table view cell.
I have apply this code below in table view but not give me the correct solution.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cellRect = tableView.rectForRow(at: indexPath)
let isFullyVisible = tableView.bounds.contains(rectInFull)
if isFullyVisible {
// Play video
}
}
So please can you tell me how can i get the correct table view cell visible
cell.frame.origin.y >= tableview.contentOffset.y && cell.frame.origin.y + cell.frame.size.height <= tableview.contentOffset.y + tableview.bounds.size.height
You might want to implement the UIScrollViewDelegate method scrollViewDidScroll(_:) and in there you could do the check for visible cells in your table view:
func scrollViewDidScroll(_ scrollView: UIScollView) {
guard let tv = scrollView as? UITableView else { return }
tv.visibleCells.forEach { $0.playVideo() }
}
Of course assuming that method on the cell (playVideo()) exists and it takes care of being called multiple times when the video is already playing (i.e. must ignore the call if is already playing).
Otherwise if you need the indexPaths of visible cells or any fine tuning use either the property indexPathsForVisibleRows or the method indexPathForRows(in:) on the table view, then those index paths you'll optionally obtain inside an array will point to the model's objects (which might implement the play video logic).
I'm implementing drag and drop in a table view, and the problem I'm having is that the drag image is always based on the column where the drag started, but I want to use the image from the first column.
I looked at Apple's TableViewPlayground sample, where the "Complex OutlineView Window" does what I want, but I can't figure out what I'm doing different in terms of setting up the drag.
I put together a simple example at https://github.com/Uncommon/TableTest - if you drag from the second column where it says "stuff", then it looks like you're dragging the word "stuff". What I want is to have the drag image always come from the first column (with the icon and name) regardless of which column you drag from.
In my data source I implement tableView(tableView:,pasteboardWriterForRow:) to enable dragging, but that doesn't include information about what column the drag started in. What am I missing from the example app to make this work?
The accepted answer didn't work for me. Instead, I had to implement the following optional function in NSTableViewDataSource:
func tableView(_ tableView: NSTableView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forRowIndexes rowIndexes: IndexSet) {
// Or whatever cell you want as the drag image
let cell = tableView.view(atColumn: 0, row: rowIndexes.first!, makeIfNecessary: false) as? NSTableCellView
session.enumerateDraggingItems(options: .concurrent, for: nil, classes: [NSPasteboardItem.self], searchOptions: [:]) {(draggingItem, idx, stop) in
if let cell = cell {
let rect = cell.bounds
let drag = session.draggingLocation
// You can customize the image starting point and size here
draggingItem.setDraggingFrame(NSRect(x: drag.x, y: drag.y, width: rect.width, height: rect.height), contents: cell.draggingImageComponents)
draggingItem.imageComponentsProvider = {
return cell.draggingImageComponents
}
}
}
}
Credit goes to this answer for providing much of this solution.
NSTableView drags the column where the drag started, NSOutlineView drags the outline column. If you want to change the image, subclass NSTableView and override dragImageForRows(with:tableColumns:event:offset:).
In response to why
dragImageForRows(with:tableColumns:event:offset:)
didn't work:
From the Apple documentation:
Note:
To support multi-item drags, it is highly recommended to implement the delegate method tableView:pasteboardWriterForRow: instead. Using that method will cause this method to not be called.
I have a custom UICollectionView and the cells are loaded in cellForItemAt but when I try to get all the visible cells by using visibleCells I'm not getting all the cells.
For example, in cellForItemAt, I'm setting the alpha of the labels in the cells to 0. When panned, I want the alpha of those labels change to 1:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
handleLabel(scrollView, active: true)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if pickerIsActive { handleLabel(scrollView, active: false) }
}
private func handleLabel(_ scrollView: UIScrollView, active: Bool) {
guard let pickerView = scrollView as? UICollectionView else { return }
let cells = pickerView.visibleCells.flatMap { $0 as? CustomCell }
panningIsActive = active
UIView.animate(duration: 0.3) {
cells.forEach { $0.label.alpha = $0.isSelected || active ? 1 : 0 }
}
}
And cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
cell.label.alpha = 0
return cell
}
What can I do to change all the "loaded" cells instead of just the "visible" cells?
The visibleCells are only the on screen cells. This used to be everything initialized in cellForItem:at: but as of iOS 10 UICollectionView now prefetches to improve scrolling performance (see WWD 2016 video) which maybe why you are having this problem. Anyways it sounds like all you want to do is animate the cells to fade in when they come on screen. You can either move your animation logic to willDisplayCell or subclass UICollectionViewCell. UIColectionViewCell inherits from UIView, so you can override didMoveToSuperView in your UICollectionViewCell and call your animation method there, which will cause the cell to animate as it appears.
I am using Xcode 11.4 and Swift 5, and I had the exactly the same issue: .visibleCells is not giving me all the loaded cells.
By reading #Josh Homann's answer and the comments below, I figured out 2 solutions.
The first solution is same as the solution you reached at: customize cell appearance in collectionView(_:willDisplay:_:) after it's loaded but before it's displayed on the screen.
Another quick and dirty solution is to simply uncheck UICollectionView's 'Prefetch' option in attributes inspector.
This fixes the issue because by disabling prefetching, UICollectionView will stop pre-loading cells that are not displayed on the screen, so .visibleCells are now all the loaded cells. This solution will work fine if you're simply loading static or small local data in the cells. If you're prefetching large data (e.g. images) from network for upcoming cells, you probably need Prefetching Enabled, then solution 1 is your go-to option.
It sounds like you might want to try using layoutAttributesForElements(in:).
You'll need to implement your own collection view layout subclass (rather than using the delegate methods) but I think it will be worth it in the long term.
Rather than manually managing the animations (via UIView.animateWithDuration) you use this method to tell the collection view what properties cells should have at different positions, and as people pan the collection view, the correct properties are automatically applied.
I tried to find a good Swift reference for this, but I could't, but here's a post in Objective-C that you can follow if you want to try this approach:
https://bradbambara.wordpress.com/2014/05/24/getting-started-with-custom-uicollectionview-layouts/
The profiles get pulled in from a server but I don't know how to implement this. Had been thinking about a dynamic table view, but I don't know if you can draw cells like that. The pictures have to be clickable.
Add A UICollectionView to your screen. In the collection view cell, place a image view, then use this code to create the blue border and circle image...
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell:UICollectionViewCell = self.collectionView.dequeueReusableCellWithReuseIdentifier("BasicCell", forIndexPath: indexPath) as UICollectionViewCell
let imageView = cell.viewWithTag(1) as! UIImageView
//border
imageView.layer.borderWidth = 2.0
imageView.layer.borderColor = UIColor.blueColor().CGColor
//make imageView Circle
imageView.layer.cornerRadius = imageView.frame.size.width / 2
imageView.clipsToBounds = true
return cell
}
UICollectionView is probably what you're looking for (to display like on your screen capture : sections / items).
UICollectionView Class Reference : https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionView_class/
It's similar as working with UITableView. Still, you might want to read some tutorials about it.
What you want is UICollectionView. Think of it as the generic version of UITableView.
I have a table view controller displaying square videos, and I want each table view cell to be a square as well. Is it possible to have each UITableViewCell dynamically resize according to the width of the screen (for example 320x320 for iPhone5 and 375x375 for iPhone6)?
In my view controller's viewDidLoad function I have:
frameWidth = self.view.frame.size.width
self.videosView.rowHeight = UITableViewAutomaticDimension
self.videosView.estimatedRowHeight = frameWidth
The constraints for the view that displays the video inside the UITableViewCell are:
Leading and trailing space to superview, top space to superview, 1:1 aspect ratio
I still can't get the cell to be a square though. Any help would be appreciated!
You can use this tableview method:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UIScreen.main.bounds.width
}
Note: Method in Swift 3
There is a good reason that this is hard to do. Cells inside UITableView is reused so it can be allocated once. (for performance)
Resizing view is performance wise costly, that's way you only put one number as height of cell.
What you can do is to have if statement and return accordingly to phone version.
try to use it with bounds instead of frame
frameWidth = view.bounds.size.width