swift UIViewController for a button in a custom cell - swift

I've a custom table view cell which has a label view. I added tapGesture to invoke a function when that view is clicked.
Currently my custom view cell is in it's own swift file. I added following code to enable 'Share extension' when clicked on that label.
CustomCellView.swift
10 myLabel.isUserInteractionEnabled = true
11 let tap = UITapGestureRecognizer(target: self, action: #selector(tapLabelGesture))
12 myLabel.addGestureRecognizer(tap)
13 func tapLabelGesture() {
14 print("Clicked on Label ")
let url="www.google.com"
15 let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
16 activityVC.popoverPresentationController?.sourceView = self.view
17 self.present(activityVC, animated: true, completion: nil)
18 }
I get compiler error on line 16 for self.view and 17 for self.present(). Question is how do I provide view for the popup?
This code I used for another view (without table view or cell) as a test and it worked fine. So I'm trying do the same technique for a tableview/cell. How do I resolve this issue? Any help is appreciated.

For line 16:
You are getting an error which says your class CustomCellView has no member view because your class is subclass of UITableViewCell not UIViewController because UIViewController has that property and your CustomCellView have contentView for that.
For line 17:
same as above your class is not subclass of UIViewController thats why you can not use self.present for that.
Solution:
Instead of using UITapGestureRecognizer you can use UIButton which you can place on UILabel and in your UIViewController class add button.tag and button.addTarget in your cellForRowAt method.
Then you can add your code in your button method and present UIActivityViewController.
Hope this will help.

As #DharmeshKheni mentioned you cannot use a subclass of UITableViewCell like UIViewController. It doesn't provide view property and present method.
Answering your question, you could store a closure in the CustomCellView:
var onLabelTappedCallback: (() -> Void)?
call it in your selector:
#objc private func tapLabelGesture() {
onLabelTappedCallback?()
}
and finally implement in the cellForRowAt method:
cell.onLabelTappedCallback = {
print("Label tapped")
//Present additional vc
}
This solution will work with UIButton as well.

I got more ideas from this thread on SO,
how to recognize label click on custom UITableViewCell - Swift 3
With the an extension, I was able to solve this issue.

Related

How to invoke a method from a modal view controller class in Swift?

Basically for this simple game app I have 2 different UIViewControllers called ViewController and PreviewController. PreviewController is opening view with the title screen and a label titled "Start game". When the label is tapped, it initiates a modal view controller (the ViewController class that has all the views for the actual game itself) and calls the "EnterNewGame" method from ViewController that sets up the game. Right now the issue I have is when calling this method, only part of the method seems to be running.
Here is the function in PreviewController that is being initiated upon tap:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
self.present(ViewController(), animated: true, completion: {() -> Void in
ViewController().enterNewGame()
})
}
And here is the EnterNewGame() method from ViewController
func enterNewGame() {
//show suit indicators when starting a new game
bluePlayerSuitsHidden = false
redPlayerSuitsHidden = false
game.blueTurn = true
self.setBackground()
self.cleanUpBoard()
self.createBoard()
self.displayBoard()
self.setSuitIndicators()
self.highlightCards()
playButton.isEnabled = false
}
Right now, when the label is tapped the screen transitions to the modal view controller but only displays a black screen with only one of the game setups (setting a few images on the top of the screen) working properly. I am sure that the EnterNewGame method works properly to actually start the game because I have tested it in isolation, so I think I am just not setting up the modal view controller properly or I have to call the method differently. Any help is appreciated, thanks.
Controller on which you're calling your method ins't the same instance as controller which you're presenting, you need constant (also your code can be simplified by avoiding using self references and writing name of completion parameter with specifing closure's parameter and return type)
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
let controller = ViewController()
present(controller, animated: true) {
controller.enterNewGame()
}
}
Also, you can call this method on some other method inside your certain controller like viewDidLoad, viewWillAppear or you can create factory method which would return you certain set controller.
This last part leads me to idea: look how you instantiate your controller and look carefully if you don't need to instantiate it through storyboard or nib file.
class ViewController: UIViewController {
class func instantiate() -> ViewController {
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Identifier") as! ViewController
// let controller = ViewController(nibName: "ViewController", bundle: nil)
controller.enterNewGame()
return controller
}
}
Usage:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
present(ViewController.instantiate(), animated: true)
}

Display a xib view into a UITableView Cell

I made a view in a xib file which is loaded in the main ViewController called "ExperienceScreen". Adding this xib view to the ExperienceScreen works perfectly. The problem is that I would like to add this xib view in a UITableViewCel. I am using the following code to do that :
let experiences = service.getExperiences()
// 3. Loop through the array of experiences
for element in experiences {
if let customView = Bundle.main.loadNibNamed("ExperienceDetail", owner: self, options: nil)?.first as? ExperienceDetail
{
customView.lblTitle.text = element.title
customView.lblCompany.text = element.company
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
cell.addSubview(customView)
cell.bringSubview(toFront: customView)
}
}
tableView.reloadData()
When launching, the subview is not shown in the UITableViewCell. The customView View is filled correctly with the xib View. I checked this using a breakpoint.
Someone knows what I'am doing wrong?
Many thanks for helping !!
If you want to display your xib file as UITableViewCell, then following scenario works
1. make sure your xib class is sub class of UITableViewCell.
2. register your xib
//class of xib file
class TableCell: UITableViewCell {
static let identifier = "TableCell"
static let nib = UINib(nibName: "TableCell", bundle: nil)
}
// In view controller
func setupTableView() {
tableView.dataSource = self
tableView.delefate = self
tableView.register(TableCell.nib, forCellReuseIdentifier:
TableCell.identifier)
}
call setupTableView() in viewDidLoad()
Don't try to set up your cells all at once.
You should implement the UITableViewDataSource protocol and configure each cell using the method tableView(_:cellForRowAt:). In this method, you can call register(_:forCellReuseIdentifier:) to use your XIB for newly created cells.
There are lots of tutorials on creating table views where you can find step-by-step instructions on doing this.

Swift: Update UITableView from UIButton inside custom UITableViewCell

I have a UITableViewController called TableVC and a custom UITableViewCell called CustomCell. Inside the CustomCell class, I have a UIButton:
#IBAction func reloadTableView(sender: AnyObject) {
TableVC().tableView.reloadData()
}
When the corresponding button is tapped, it does not seem to update/reload my table view. Why not, and what can I do to resolve my issue?
Thanks!
You can get the tableView of a cell in several ways. The superview of your cell should be of type UITableView so in your cell you can (superview as? UITableView)?.reloadData().
However a more stable method is using the hierarchy of responders. I have created a really useful extension that allows you to find the next of responder of a particular type such a UITableView.
extension UIResponder {
func nextResponder<T: UIResponder>(ofType type: T.Type) -> T? {
switch nextResponder() {
case let responder as T:
return responder
case let .Some(responder):
return responder.nextResponder(ofType: type)
default:
return nil
}
}
}
This is a recursive function, so it climbs the responder hierarchy until it successfully casts a responder to the provided type or runs out of responders to try and cast.
#IBAction func reloadTableView(sender: AnyObject) {
nextResponder(ofType: UITableView.self)?.reloadData()
}
Whenever you are clicking a button you are creating a new instance of TableVC and you are reloading the that tableview data. instead of this store a TableVC object somewhere and reload data on that. either create a IBOutlet of TableVC or create a property in a class and assign TableVC ref to that.
use only
self.tableView.reloadData()

Using UINavigationBarDelegate method shouldPopItem in Swift

I've been trying to figure out how to use the UINavigationBarDelegate method shouldPopItem with a popover in Swift. I've done lots o' digging around and trying this and that, with no success. I'm hopeful someone can point me in the right direction.
I start with a UIViewController that has a number of buttons the user can click. As an example, there is a button that calls back to this method:
#IBAction func manageVerbListsButtonPressed(sender: UIButton) {
vc = ManageListsViewController(nibName: "ManageListsView", bundle: nil)
vc.preferredContentSize = CGSizeMake(600, 600)
vc.modalPresentationStyle = .Popover
let popoverController = vc.popoverPresentationController!
popoverController.sourceView = sender
popoverController.sourceRect = sender.bounds
popoverController.permittedArrowDirections = .Left
popoverController.delegate = self // could you set the popover delegate to the vc, so the vc could control the dismiss?
presentViewController(vc, animated: true, completion: nil)
}
This correctly opens a popover with ManageListsView as the presenting controller. That controller has a view with a table. When the user clicks on a table row, the didSelectRowAtIndexPath method fires:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let vc = EditOneListViewController()
let vl = Lists[indexPath.row] // this is an array of list names
vl.isNew = false
vc.List = vl // give the next controller the list to edit
navigationController?.pushViewController(vc, animated: true)
}
Again, this correctly pushes the EditOneListViewController.
What I want to do, but haven't figured out how, is to use the UINavigationBarDelegate method shouldPopItem to determine whether the navController should pop back to the table view, depending on whether the user has done something on the EditOneListViewController view. That is, if the user has edited something and not saved it, I want to use the shouldPopItem to put up an Alert indicating that and return to the view so the user can save the edits. (The list has an isDirty boolean that I can test for whether it's been saved.) If the user has saved edits, I want the navController to pop back a level.
I have done this in Obj C in earlier iOS's, but I'm darned if I can figure out how to do it with iOS 9 and Swift and presentationControllers. Any help at all will be greatly appreciated.

Storyboard UIView Objects Not Instantiating

I am working on a project with Swift and Storyboards. It's a conversion project from a traditional IB and Objective-C project. I am having an issue with a UITableView instantiating when the view is loaded. Let me explain.
The project is a navigation project. Here is an overview of the Storyboard.
The Storyboard's first viewController is HomeViewController and is a landing page that displays general info. The next VC is called FeedViewController shows a number of RSS feeds. You can see an expanded screen shot of the NavigationController, HomeViewController and FeedViewController in the picture below.
My problem is that I can't get the tableView to Instantiate. I first checked to make sure that my tableView was connected as an outlet and that the dataSource and delegate properties were connected. You can see this in the pic below.
In my FeedViewController class I have an Outler property called feedsTableView. You can see the declaration in the code below.
class FeedViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FLODataHandlerDelegate
{
// View Contoller and Protocol Properties
var floView : FLOViewController?
var dataHandler : FLODataHandler?
// Interface and Content Properties
var refreshControl : UIRefreshControl?
// IBOutlets
#IBOutlet weak var feedsTableView: UITableView!
#IBOutlet weak var backgroundImage: UIImageView!
In the HomeViewController I have a FeedViewController property that I intend to use to gain access to FeedViewController's feedsTableView.
class HomeViewController: UIViewController, FLODataHandlerDelegate, MFMailComposeViewControllerDelegate
{
// View Contoller and Protocol Properties
var feedViewController : FeedViewController?
var dataHandler : FLODataHandler?
When HomeViewController's viewDidLoad() method is called I start the dataHandler - which instantiates the FeedViewController - and set it to my FeedViewController property.
override func viewDidLoad()
{
super.viewDidLoad()
// Set up the gesture recognizer to allow for swiping to the feed VC.
let recognizer = UISwipeGestureRecognizer(target: self, action: Selector("goToNext"))
recognizer.direction = UISwipeGestureRecognizerDirection.Left
self.view.addGestureRecognizer(recognizer)
// Start the data handler
self.setUpDataHandler()
}
setUpDataHandler()
func setUpDataHandler()
{
// Intitalize FeedVC for use later in the VC
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("FeedViewController") as! FeedViewController
self.feedViewController = vc
}
I also have a fail safe that if someone were to go to the FeedViewController before the setUpDataHandler() method is called then I instantiate FeedViewController here as well.
func goToNext()
{
// Grab the feedViewController so it can be pushed onto the stack. Make sure you set up the storyboard identifier.
let feedVC = self.storyboard!.instantiateViewControllerWithIdentifier("FeedViewController") as! FeedViewController
self.feedViewController = feedVC
self.navigationController!.pushViewController(self.feedViewController!, animated: true)
}
However the feedsTableView is not getting instantiated. In the viewDidLoad() method of FeedViewController I attempt to add the feedsTableView to a UIRefreshController.
override func viewDidLoad()
{
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
self.refreshControl!.addTarget(self, action: "refreshInvoked:state:", forControlEvents: UIControlEvents.ValueChanged)
// See the note in viewDidLoad in FLOViewController.
self.feedsTableView.addSubview(self.refreshControl!)
}
When the app runs I get the following error.
fatal error: unexpectedly found nil while unwrapping an Optional value
The image below shows were this is called. It's the viewDidLoad() of the FeedViewController. As you can see in the picture I even tried instantiating the feedsTableView before adding it to the UIRefreshController and I still get the error.
Any help would be greatly appreciated.
Take care,
Jon
The reason why it doesn't work in the very last case, where you manually instantiate UITableView and assign that to self.feedsTableView, is that self.feedsTableView is declared weak. Thus, the table view comes into existence, is assigned, and vanishes in a puff of smoke because it has no memory management. By the time you get to the last line, self.feedsTableView is nil once again.
Thus, the solution for that last case is to remove the weak designation from your feedsTableView declaration.
That will get you past the crash in that last case. But of course you won't see anything because you are not also inserting the table view into your interface.