SourceView and SourceRect is depreciated? - swift

I'm trying to fix all my warnings for my project, but I can't seem to figure out how to do this.
The warning I am given is:
'sourceView' was deprecated in iOS 13.0: renamed to 'UIContextMenuInteraction'
I have read the documentation here but i still cant figure out how to fix this warning?
Here is the code it is talking about:
extension CollectionsViewController: UIViewControllerPreviewingDelegate {
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
let tableView = previewingContext.sourceView as! UITableView
if let indexPath = tableView.indexPathForRow(at: location) {
let cell = tableView.cellForRow(at: indexPath) as! CollectionCell
let touch = cell.convert(location, from: tableView)
if let productResult = cell.productFor(touch) {
previewingContext.sourceRect = tableView.convert(productResult.sourceRect, from: cell)
return self.productDetailsViewControllerWith(productResult.model)
} else if let collectionResult = cell.collectionFor(touch) {
previewingContext.sourceRect = tableView.convert(collectionResult.sourceRect, from: cell)
return self.productsViewControllerWith(collectionResult.model)
}
}
return nil
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
self.navigationController!.show(viewControllerToCommit, sender: self)
}
}
Any help would be greatly appreciated!

UIViewControllerPreviewingDelegate protocol changed as documented:
Deprecated
UseUIContextMenuInteractionDelegate instead.
So you need to remove previewingContext delegate method rather than change its parameter sourceview etc. Instead of this method you need to pick from the new protocol UIContextMenuInteractionDelegate of its
delegate methods as Apple indicated.
In other words your extension is totally outdated and you need to rewrite according to new protocol.

Related

Usability of a button inside a UICollectionViewCell?

I have a ProductVC.swift (ProductViewController) file and a ProductCell.swift. The ProductVC contains a UICollectinView and ProductCell is a specific UICollectionViewCell.
ProductCell.xib looks like this:
ProductVC contains an array with all the cell data (products) and populates the cells.
My goal: The user should have the possibility to like an product. He can do it by clicking the like button on the top right corner of every cell. Every cell shows a specific product which is specified by a productID.
My Problem: The like button action (IBAction func) is in the ProductCell. ProductCell doesn´t have the cell data. Cell data is stored in ProductVC in an array. So I don´t know how catch the product(productID) the user wants to like.
My Tries: With the code below I can get the indexPath of the cell where the user clicked the like button. But I can´t use this indexPath to get the product data because the data is stored in ProductVC. I could also store the data in ProductCell but it is not a clean way. Is it possible mb to give this indexPath to the ProductVC?
extension UICollectionView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let center = view.center
let viewCenter = self.convert(center, from: view.superview)
let indexPath = self.indexPathForItem(at: viewCenter)
return indexPath
}
}
let superview = self.superview as! UICollectionView
if let indexPath = superview.indexPathForView(button) {
print(indexPath) // indexPath of the cell where the button was pressed
}
SOLVED Solution is a callback closure:
//UICollectionViewCell
var saveProductLike: ((_ index: Int) -> Void)?
#IBAction func likedButtonClicked(_ sender: UIButton) {
print("Liked button clicked!")
let productArrayIndex = calculateProductArrayIndex(for: sender)
saveProductLike?(productArrayIndex!)
}
//UIViewController
cell.saveProductLike = { (index) -> Void in
print(index)
}
There are several approaches to solve this but I'll talk about the most common one which is using delegation.
protocol ProductCellDelegate: AnyObject {
func productCellDidPressLikeButton(_ cell: ProductCell)
}
in ProductCell define a property weak var delegate: ProductCellDelegate? and in the target action of the like button inform your delegate
#objc private likeButtonPressed(_ sender: Any) {
delegate?.productCellDidPressLikeButton(self)
}
In your view controller you could conform to the protocol and implement it like this:
func productCellDidPressLikeButton(_ cell: ProductCell) {
guard let ip = collectionView.indexPath(for: cell) else { return }
// process event, get product via index...
}
Then you need to set the view controller to be the delegate in collectionView(_:willDisplay:forItemAt:) or
collectionView(_:cellForItemAt:): cell.delegate = self.

Different behavior between T.Type & [T.Type]

I am using generics func to improve tableView cells as below;
public func registerNib<T: UITableViewCell>(_:T.Type) {
print("Registering.....")
print(T.self)
let nib = UINib(nibName: String(describing: T.self), bundle: nil)
register(nib, forCellReuseIdentifier: String(describing: T.self))
}
And I wrote two func to register cell, one for single cell and one for multiple cells.
//Single Cell Register
func setup<T: UITableViewCell>(cell:T.Type) {
print(cell)
tableView.registerNib(cell)
self.setupParameters(.....)
}
//Multiple Cell Register
func setup<T: UITableViewCell>(cells:[T.Type]) {
for cell in cells {
print(cell)
tableView.registerNib(cell)
}
self.setupParameters(.....)
}
For single cell implementation, you can call func as below and it will registerNib correctly.
self.baseTableView.setup(cell: CompetencyCell.self, rowHeight: 60)
**************
Output:
CompetencyCell
Registering...
CompetencyCell
But problem is occur with multiple implementation;
self.baseTableView.setup(cells: [CompetencyCell.self, BehaviorCell.self], rowHeight: 60)
**************
Output:
CompetencyCell
BehaviorCell
Registering...
UITableViewCell
UITableViewCell
I really wonder what is the reason, or what I miss? It looks like same behavior but when I want to pass T.Type value to RegisterNib method it turns to UITableViewCell and after some point of course I got crash because it could not find the cell.
Do you have any advice?
Thanks a lot.
When calling a generic function the generic type T represents a single static type.
[T.Type] is nonsensical because it declares an array of the same type specifier.
T doesn't mean Any!
As described in vadian's answer, generic is not a good tool to use with Array containing multiple types of values.
You can try something like this:
extension UITableView {
public func registerNib(_ type: UITableViewCell.Type) {
print("Registering.....")
print(type)
let nib = UINib(nibName: String(describing: type), bundle: nil)
register(nib, forCellReuseIdentifier: String(describing: type))
}
}
And this:
func setup(cells: [UITableViewCell.Type] /*, otherParams: ...*/) {
for cell in cells {
print(cell)
tableView.registerNib(cell)
}
//self.setupParameters(.....)
}

protocols & viewControllers, passing info using protocols on the same action Func Swift 3 & 4

I have this code to pass info between viewControllers using protocols, I have my models and with the first protocol work perfectly but the second one I have some issues, the data pass nil, or do nothing, already use tabBarController & UINavigationBar... the protocols... I create one in my ViewController(RecordViewController) and have the button & action and one protocol, this is:
protocol RecordViewProtocol {
func newTrackInstrument(item: SampleCarouselsRecord)
func newInstrumentTrackCell(item: SampleTrackCellRecord)
}
extension RecordViewController: RecordViewProtocol {
func newTrackInstrument(item: SampleCarouselsRecord) {
self.sampleCarouselItemsRecord.append(item)
let indexOnPath = NSIndexPath(row: self.sampleCarouselItemsRecord.count - 1, section: 0)
self.instrumentCarousel.insertItems(at: [indexOnPath as IndexPath])
}
func newInstrumentTrackCell(item: SampleTrackCellRecord) {
self.sampleTrackRecord.append(item)
let indexOnPath = NSIndexPath(row: self.sampleTrackRecord.count - 1, section: 0)
self.trackInstrument.insertItems(at: [indexOnPath as IndexPath])
}
}
the other protocol, on another ViewController(MixerViewController):...
protocol MixerViewProtocol {
func newCarouselItem(item: SampleCarouselMixer)
}
extension MixerViewController: MixerViewProtocol {
func newCarouselItem(item: SampleCarouselMixer) {
self.sampleCarouselMixer.append(item)
let indexOnPath = NSIndexPath(row: self.sampleCarouselMixer.count - 1, section: 0)
self.mixerCarouselInstrument.insertItems(at: [indexOnPath as IndexPath])
}
}
so, when wanna go the view and put the info to my models.. I put this code in the action Button to go into the view where is the function to put the info models:
#objc func goToAddView() {
let addTrackViewController = AddNewTrackView()
let addTrackView = UINavigationController(rootViewController: addTrackViewController)
addTrackViewController.recordViewControllerProtocol = self as RecordViewProtocol
present(addTrackView, animated: true, completion: nil)
}
and work good with the first protocol.. but with to the other protocol nothing.. I don't know why or I don't know wath I have to do... look, I have the delegate protocols inside on the AddNweTrackView:
var recordViewControllerProtocol: RecordViewProtocol?
var delegateMixerView: MixerViewProtocol? <- this is I think, put into the action button like the firstone:
here on the same action where the first protocol already is
#objc func goToAddView() {
let addTrackViewController = AddNewTrackView()
let addTrackView = UINavigationController(rootViewController: addTrackViewController)
addTrackViewController.recordViewControllerProtocol = self as RecordViewProtocol
addTrackViewController.delegateMixerView = MixerViewController.self as? MixerViewProtocol
present(addTrackView, animated: true, completion: nil)
}
but return nil... and I try with diferents forms.. I try put this:
let mixerViewController = MixerViewController()
addTrackViewController.delegateMixerView = mixerViewController as? MixerViewProtocol
and try with this but the error Is obvious:
addTrackViewController.delegateMixerView = self as MixerProtocol
and the same... I think, here at this point, it's where Im failing .. someone can help?

How to make a table View with message showing if tableview empty?

I am building a tableView which displays a message when empty.
I'm using the really helpful answers on this question (Handling an empty UITableView. Print a friendly message) This has led me to a function:
func emptyMessage(message:String, viewController:UITableViewController) {
let VCSize = viewController.view.bounds.size
let messageLabel = UILabel(frame: CGRect(x:0, y:0, width:VCSize.width, height:VCSize.height))
messageLabel.text = message
messageLabel.textColor = UIColor.black
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .center;
messageLabel.font = UIFont(name: "Futura", size: 15)
messageLabel.sizeToFit()
viewController.tableView.backgroundView = messageLabel;
viewController.tableView.separatorStyle = .none;
}
I could call this in every table views data source like this :
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if projects.count > 0 {
return 1
} else {
TableViewHelper.EmptyMessage("You don't have any projects yet.\nYou can create up to 10.", viewController: self)
return 0
}
}
which would work. However I would rather not have to implement that repeatedly and instead have one custom tableview with a method in the data source asking what message you would like to add.
I've tried extending the TableView class or making a subclass of tableView but I think this isn't the solution. Instead I think the solution is to overwrite the UITableViewDataSource protocol but my knowledge of protocols and delegation isn't sufficient.
I hope i'm on the right track with this. And to clarify I could do it in the way mentioned above but i'm trying to override the functionality to make a smart solution where i'm not repeating myself.
There is a very good library :
https://github.com/dzenbot/DZNEmptyDataSet
This can be used for all types of containers like UITableView, UICollectionView etc.
After conforming to some DZNEmptyDataSetSource, DZNEmptyDataSetDelegate , you can simply implement these functions:
func title(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
let str = "Empty Data."
let attrs = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)]
return NSAttributedString(string: str, attributes: attrs)
}
func description(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
let str = "Any Details about empty data."
let attrs = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)]
return NSAttributedString(string: str, attributes: attrs)
}
Apart from that, you can add a button to to perform some actions. Please refer the library for more features.

Swift: programmatically enumerate outgoing segues from a UIVIewController

I want to list the outgoing segues from a UIViewController, as described in Programmatically enumerate outgoing Segues for a UIViewController, but in Swift. (Swift 2, Xcode 7, iOS8+).
I can do
override func viewDidLoad() {
super.viewDidLoad()
let s = valueForKey("storyboardSegueTemplates")
print("switchingVC: segues: \(s)")
}
and that produces output like
switchingVC: segues: Optional((
"<UIStoryboardPresentationSegueTemplate: 0x1754a130>",
"<UIStoryboardPresentationSegueTemplate: 0x17534f60>",
"<UIStoryboardPresentationSegueTemplate: 0x17534fc0>"
))
but I struggle to produce anything after that. I can't find any definition of the UIStoryboardPresentationSegueTemplate. How can I persuade Swift to tell me what's inside it? How can I find the segue identifier?
Thanks!
this valueForKey("storyboardSegueTemplates") is UNDOCUMENTED property and UIStoryboardPresentationSegueTemplate is UNDOCUMENTED class. Beware of rejection from App Store if you are uploading application to App Store.
If you want to use this in your in-house projects, use as following
for template in (valueForKey("storyboardSegueTemplates") as? [AnyObject])! {
if let identifier = template.valueForKey("identifier") as? String {
print("identifier - " + identifier)
}
else {
print("no identifier for \(template)")
}
}
Found from https://github.com/JaviSoto/iOS9-Runtime-Headers/blob/master/Frameworks/UIKit.framework/UIStoryboardSegueTemplate.h
As per Swift 4.2 and from https://stackoverflow.com/a/35060917/1058199. Thanks /johnykutty.
import UIKit
extension UIViewController {
// Segue aids in Swift
#objc func isValidSegue(_ segueId: String?) -> Bool {
let filteredArray = (value(forKey: "storyboardSegueTemplates") as? NSArray)?.filtered(using: NSPredicate(format: "identifier = %#", segueId ?? ""))
let isValid = (filteredArray?.count ?? 0) > 0
return isValid
}
#objc func segues() -> Array<Any>? {
let segues = self.value(forKey: "storyboardSegueTemplates")
return segues as! Array<Any>?
}
#objc func segueNames() -> Array<AnyHashable> {
var segueNames = Array<Any>()
let filteredArray = (value(forKey: "storyboardSegueTemplates") as? NSArray)?.filtered(using: NSPredicate(format: "identifier != nil" ))
for template in filteredArray! as [AnyObject] {
if let identifier = (template.value(forKey: "identifier") as? String) {
segueNames.append(identifier)
}
else {
segueNames.append("no identifier for \(template)")
}
}
return segueNames as! Array<AnyHashable>
}
}
I know my use of predicates could be better, but Swift is such a PITA when dealing with iterating arrays. Please feel free to improve this.