How to activate share extension (UIActivityViewController) when a button tapped (which is in UICollectionViewController)? - swift

I'm trying to add some buttons or labels to one of UICollectionViewCell, but it seems I'm not be able to activate UIActivityViewController when the share button tapped.
Here are my codes:
class VerticalCollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
containerView.addSubview(shareButton)
shareButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16).isActive = true
shareButton.rightAnchor.constraint(equalTo: favoriteButton.leftAnchor, constant: -16).isActive = true
shareButton.widthAnchor.constraint(equalToConstant: 24).isActive = true
shareButton.heightAnchor.constraint(equalToConstant: 24).isActive = true
shareButton.addTarget(self, action: #selector(handleShareButton), for: .touchUpInside)
}
#objc func handleShareButton(sender: UIButton) {
let shareText = NSLocalizedString("I found something interesting you may want to take a look at.", comment: "share")
let shareImage = #imageLiteral(resourceName: "Bear")
let activityViewController : UIActivityViewController = UIActivityViewController(activityItems: [shareText, shareImage as Any], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}
The errors are:
Value of type 'VerticalCollectionViewCell' has no member 'view'
and
Value of type 'VerticalCollectionViewCell' has no member 'present'.
I know presentViewController is a UIViewController method, UITableViewCell does not has a method called presentViewController, and UITableViewCell should never handle any business logic. It should be implemented in a view controller.
But I have no idea how to fix this.

you call do in this way
class Cell: UITableViewCell {
#IBOutlet let shareButton: UIButton!
}
class VC: UIViewController, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? Cell {
cell?.shareButton.addTarget(self, action: #selector(VC.shareButtonPressed(_:)), for: .touchUpInside)
}
}
func shareButtonPressed(_ button: UIButton) {
//code here
}
}

What you can do, is define a protocol of the cell, which your tableview can conform to. When the share button on a collectionView is tapped, call this delegate method, passing in relevant information like the cell tag or the model for that collectionView cell. Using that information you can then present the ActivityController from your viewController.

Related

Change Button image from view controller inside custom header cell

I have a VC that is UICollectionView that has header. This header is a custom cell, and inside that cell there is an button with image which I need to change based on info from API or user tap.
In func loadData i get info about icon and then I call function inside headerCell to change the icon. Do I need to do this in other way in order for this to work?
protocol HeaderCellDelegate: class {
func setDropdown()
func tapOnSelection()
}
class HeaderCell: UIGestureRecognizerDelegate {
// delegate
weak var hDelegate: HeaderCellDelegate?
#objc
lazy var selectionBtn: UIButton = {
let button = UIButton(type: .system)
button.setTitle("One", for: .normal)
button.addTarget(self, action: #selector(changeSelection), for: .touchUpInside)
return button
}()
#objc
lazy var oneOrTwo: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "oneOff")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.addTarget(self, action: #selector(toggleOneOrTwo), for: .touchUpInside)
return button
}()
#objc
lazy var iconBtn: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "iconGreen")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.addTarget(self, action: #selector(setIcon), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
// I setup view helper
// Just a button and icon on the right
// button opens DropDown list
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Delegate Functions
#objc
func toggleOneOrTwo(_ sender: Any) {
self.hDelegate?.setDropdown()
}
#objc
func changeSelection(sender: UIButton) {
self.hDelegate?.tapOnSelection()
}
#objc
func setIcon() {
isIconGreen
? iconColor.setImage(UIImage(named: "iconGreen")?.withRenderingMode(.alwaysTemplate), for: .normal)
: iconColor.setImage(UIImage(named: "iconRed")?.withRenderingMode(.alwaysTemplate), for: .normal)
}
}
ViewController
class ViewController: UICollectionViewController, UIGestureRecognizerDelegate {
fileprivate let headerCellId = "headerCellId"
lazy var headerCell = HeaderCell()
override func viewDidLoad() {
super.viewDidLoad()
// Register Cell Class
self.collectionView.register(HeaderCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerCellId)
loadData()
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerCellId, for: indexPath) as! HeaderCell
header.backgroundColor = .groupTableViewBackground
header.hDelegate = self
return header
default:
fatalError("This should never happen!!")
}
}
func loadData() {
// get data from API and isIconGreen value
let isIconGreen = true
headerCell.setIcon(isIconGreen: isIconGreen)
}
}

Found Nil Using Protocol in TableViewCell

I'm trying to update my model using data from a textField in a custom cell. I set up a protocol in the cell's class and send the info to my ViewController, however I continually get "Found nil while implicitly unwrapping an Optional value". What am I missing? Thanks!
protocol UpdateDelegate {
func didUpdate (someText: String)
}
class customTableViewCell: UITableViewCell {
var updateDelegate: UpdateDelegate!
#IBOutlet weak var someDescriptionField: UITextField!
#IBAction func someDescriptionField(_ sender: UITextField) {
updateDelegate.didUpdate(someText: sender.text ?? "") //error is here
}
}
extension ViewController : UpdateDelegate {
func didUpdate (someText: String) {
print(someText)
}
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, UIGestureRecognizerDelegate {
//....
viewDidLoad() {
self.tableView.delegate = self
self.tableView.dataSource = self
}
}
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! TableViewCell
cell.someDescriptionField.text = meal.arrayOfPossibleDishes[indexPath.section].arrayOfSteps[indexPath.row-1].stepName
cell.layer.cornerRadius = 10
return cell
}
Very simple answer here: You are never setting the delegate on your cell. Since your ViewController class conforms to your UpdateDelegate protocol you can update your cellForRow method like so:
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! TableViewCell
//set the delegate when setting up the cell
cell.updateDelegate = self
cell.someDescriptionField.text = meal.arrayOfPossibleDishes[indexPath.section].arrayOfSteps[indexPath.row-1].stepName
cell.layer.cornerRadius = 10
return cell

Handling Previous, Next and Done buttons for keyboard across UITableView custom cells

First of all, I'm a fairly new to Swift and I've been looking for a good solution to handle Previous, Next and Done buttons on a keyboard across different custom cells in UITableView. I've looked at the various solutions on Stack Overflow but none of them fit 100% for what I need.
My tableview has one field (UITextField, UITextView, etc.) per row and need a generic way to move from one cell to the next. Some of the solutions don't account for scenarios where the next cell might be offscreen.
I've come up with a solution which I'll post as an answer. Feel free to comment on ways to improve if you have suggestions!
Please check this library. Simple and effective. You just to need to install via cocoa pods and single line code in appDelegate
pod 'IQKeyboardManagerSwift'
https://github.com/hackiftekhar/IQKeyboardManager
In App delegate
IQKeyboardManager.sharedManager().enable = true
For my custom cells, I have a base class as a foundation since I'm creating everything programmatically. It looks like this:
class BaseTableViewCell: UITableViewCell {
weak var delegate: BaseTableViewCellDelegate? = nil
var indexPath: IndexPath? = nil
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
fatalError("BaseTableViewCell setupViews not overridden")
}
func handleBecomeFirstResponser() {
// handle in derived class
}
func handleResignFirstResponder() {
// handle in derived class
}
}
Also, I've got a delegate for this base class that looks like this:
protocol BaseTableViewCellDelegate: class {
func cellEdited(indexPath: IndexPath)
func cellPreviousPressed(indexPath: IndexPath)
func cellNextPressed(indexPath: IndexPath)
func cellNeedsResize(indexPath: IndexPath)
}
// Using extension to provide default implementation for previous/next actions
// This makes then optional for a cell that doesn't need them
extension BaseTableViewCellDelegate {
func cellPreviousPressed(indexPath: IndexPath) {}
func cellNextPressed(indexPath: IndexPath) {}
func cellNeedsResize(indexPath: IndexPath) {}
}
I'm using an extension for a pure-swift mechanism to make the previous and next implementations optional in case we have a situation where we don't need these buttons.
Then, in my BaseTableViewCell class, I've got a function to setup the keyboard toolbar like this (below). I've got another function to support a UITextView as well (there might be a better way to do this; not sure).
func setupKeyboardToolbar(targetTextField: UITextField, dismissable: Bool, previousAction: Bool, nextAction: Bool) {
let toolbar: UIToolbar = UIToolbar()
toolbar.sizeToFit()
var items = [UIBarButtonItem]()
let previousButton = UIBarButtonItem(image: UIImage(imageLiteralResourceName: "previousArrowIcon"), style: .plain, target: nil, action: nil)
previousButton.width = 30
if !previousAction {
previousButton.isEnabled = false
} else {
previousButton.target = self
previousButton.action = #selector(toolbarPreviousPressed)
}
let nextButton = UIBarButtonItem(image: UIImage(imageLiteralResourceName: "nextArrowIcon"), style: .plain, target: nil, action: nil)
nextButton.width = 30
if !nextAction {
nextButton.isEnabled = false
} else {
nextButton.target = self
nextButton.action = #selector(toolbarNextPressed)
}
items.append(contentsOf: [previousButton, nextButton])
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard))
items.append(contentsOf: [spacer, doneButton])
toolbar.setItems(items, animated: false)
targetTextField.inputAccessoryView = toolbar
}
Here are the associated action routines:
func toolbarPreviousPressed() {
if delegate != nil && indexPath != nil {
delegate?.cellPreviousPressed(indexPath: indexPath!)
}
}
func toolbarNextPressed() {
if delegate != nil && indexPath != nil {
delegate?.cellNextPressed(indexPath: indexPath!)
}
}
In my view controller where I have the tableview, my cellForRowAt function has this code:
let cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell") as! TextFieldCell
let addressItem = (item as! XXXXXXAddressViewModelItem)
cell.textField.placeholder = addressItem.placeHolderText
cell.textField.text = addressItem.getValue(row: 0)
cell.indexPath = indexPath
cell.delegate = self
cell.setupKeyboardToolbar(targetTextField: cell.textField, dismissable: true, previousAction: false, nextAction: true)
return cell
Here is how I handle the delegate methods for the previous and next buttons being pressed:
func cellPreviousPressed(indexPath: IndexPath) {
// Resign the old cell
let oldCell = tableView.cellForRow(at: indexPath) as! BaseTableViewCell
oldCell.handleResignFirstResponder()
// Scroll to previous cell
let tempIndex = IndexPath(row: indexPath.row, section: indexPath.section - 1)
tableView.scrollToRow(at: tempIndex, at: .middle, animated: true)
// Become first responder
let cell = tableView.cellForRow(at: tempIndex) as! BaseTableViewCell
cell.handleBecomeFirstResponser()
}
func cellNextPressed(indexPath: IndexPath) {
// Resign the old cell
let oldCell = tableView.cellForRow(at: indexPath) as! BaseTableViewCell
oldCell.handleResignFirstResponder()
// Scroll to next cell
let tempIndex = IndexPath(row: indexPath.row, section: indexPath.section + 1)
self.tableView.scrollToRow(at: tempIndex, at: .middle, animated: true)
// Become first responder for new cell
let cell = self.tableView.cellForRow(at: tempIndex) as! BaseTableViewCell
cell.handleBecomeFirstResponser()
}
Finally, in my cell class derived from BaseTableViewCell, I override the handleBecomeFirstResponder and handleResignFirstResponder like so:
override func handleBecomeFirstResponder() {
textField.becomeFirstResponder()
}
override func handleResignFirstResponder() {
textField.resignFirstResponder()
}
On a related note, I handle the keyboard show and hide notifications by using insets on the tableview:
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, (keyboardFrame?.height)!, 0)
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, (keyboardFrame?.height)!, 0)
and:
self.tableView.contentInset = UIEdgeInsets.zero
self.tableView.scrollIndicatorInsets = UIEdgeInsets.zero
This took me a lot of trial-and-error to get this right and not seriously convolute my view controller with code that should be in the cell class.
I'm always looking for better ways to do this. Let me know your thoughts!

Updating button events in Swift?

I am creating a Check List app and trying to update button event which is set images itself after clicked.
Here is my code:
protocol CustomeCellDelegate {
func cell(cell: UITableViewCell, updateTheButton button: UIButton)
}
class Cells: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var checkBox: UIButton!
var buttonPressed = true
#IBAction func checkBoxAction(_ sender: UIButton) {
if buttonPressed {
sender.setImage(UIImage(named: "checkBoxB"), for: UIControlState.normal)
buttonPressed = false
} else {
sender.setImage(UIImage(named:""), for: UIControlState.normal)
buttonPressed = true
}
}
#objc func recordButtonImage(_ button: UIButton) {
cellDelegate?.cell(cell: self, updateTheButton: button) // cell notify the button that touched.
}
override func awakeFromNib() {
checkBox.addTarget(self, action: #selector(recordButtonImage(_:)), for: UIControlEvents.touchUpInside)
}
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, CustomeCellDelegate {
#IBOutlet weak var tableView: UITableView!
var myImages: [UIImage?]!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
myImages = Array(repeatElement(nil, count: 50))
let nib = UINib(nibName: "Cells", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "Cells")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
internal func tableView(_ tableView:UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "theCells") as? Cells
cell?.theTextField.delegate = self
cell?.cellDelegate = self
cell?.checkBox.setImage(myImages[indexPath.row], for: UIControlState.normal)
return cell!
}
// This function from protocol and it helps to update button images.
func cell(cell: UITableViewCell, updateTheButton button: UIButton) {
print("Delegate method Update Button Images.")
if let indexPath = tableView.indexPath(for: cell), let buttonImage = button.image(for: UIControlState.normal) {
myImages[indexPath.row] = buttonImage
}
}
I would like to update both events of the button that set image after checked and unchecked. Therefore, my table view can dequeue Reusable Cell and have those events updated.
But the result is just one event of button which is checked updated but not the unchecked one. The checked button still repeats whatever I do to uncheck it.
I think you don't need recordButtonImage method, you should call the delegate method from the button tapped action i.e. checkBoxAction, just like below
class Cells: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var checkBox: UIButton!
var buttonPressed = true
#IBAction func checkBoxAction(_ sender: UIButton) {
if buttonPressed {
sender.setImage(UIImage(named: "checkBoxB"), for: UIControlState.normal)
buttonPressed = false
} else {
sender.setImage(UIImage(named:""), for: UIControlState.normal)
buttonPressed = true
}
// cell notify the button that touched.
cellDelegate?.cell(cell: self, updateTheButton: button)
}
}
Also your data source method, doesn't tell the cell whether the button was pressed or not. It just sets the image but doesn't set the variable buttonPressed
internal func tableView(_ tableView:UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "theCells") as? Cells
cell?.theTextField.delegate = self
cell?.cellDelegate = self
cell?.checkBox.setImage(myImages[indexPath.row], for: UIControlState.normal)
return cell!
}
The best way to handle UIButton is using its own state.
You need to hold current state value in one array. So you can keep the state in cellForRow and other things. By default set all state to false.
In CellForRow, just put below code:
YOUR_BUTTON.isSelected = aryState[indexPath.row] as! Bool
Set image for your button
YOUR_BUTTON.setImage(UIImage(named: NORMAL_IMAGE), for: .normal)
YOUR_BUTTON.setImage(UIImage(named: SELECTED_IMAGE), for: .selected)
Just change button state when it clicked:
#IBAction func checkBoxAction(_ sender: UIButton) {
let button = sender
button.isSelected = !button.isSelected
}

button click function in custom cell based on obtained indexpath of current cell

I need to know how to write segue code or controller code inside a custom cell.
if we can't do that, how can i get the indexpath of particular custom cell on button click inside that custom cell.
Things i did,
a ViewController with UITableView that populates the value from api, on the custom cell.
custom cell consists of some lines of details and two buttons.
one button is to make phone call function.
another button is to get direction in map-kit.
coming for the api, it has values for making phone call and to get direction(it contains latitude and longitude).
from the custom cell class i'm perform the phone call function(No Problem with this.).
i'm having problem with get direction function only.
here goes the problem explanation,
codes:
my custom cell class
class Usercell: UITableViewCell {
var phoneNumber = "" // get the phone number
var latlng:NSArray = []
#IBOutlet weak var vendorName3: UILabel!
#IBOutlet weak var vendorAdddress3: UILabel!
#IBOutlet var FeaturedDisplayText: UILabel!
#IBOutlet weak var getDirectionButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
// action method for call button tap
#IBAction func CallbuttonTap(sender: AnyObject) {
let phoneString = "tel://\(phoneNumber)"
let openURL = NSURL(string:phoneString)
if openURL == nil {
return
}
let application:UIApplication = UIApplication.sharedApplication()
if (application.canOpenURL(openURL!)) {
application.openURL(openURL!)
}
}
#IBAction func GetDirectionButtonTapped(sender: AnyObject) {
print("Get direction button tapped.")
print("LatLng for this cell is:",latlng)
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
in the above code basics display functions are working.
I have written that UIButton cell click function in cellForRowAtIndexPath
cell1.getDirectionButton.addTarget(self, action: #selector(ContainerViewController.GetDirection(_:)), forControlEvents: UIControlEvents.TouchUpInside)
like that above.(Note: ContainerViewController is my ViewController which contains my tableView with this custom cell.)
GetDirection() contains
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: mapvc2 = storyboard.instantiateViewControllerWithIdentifier("mapvc2") as! mapvc2
let indexPath = ???
let DestinationLocation = CLLocationCoordinate2D(latitude: val[indexPath.row].latlng[0] as! Double, longitude: val[indexPath.row].latlng[1] as! Double)
vc.PlotRoute(DestinationLocation)
vc.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "< back", style: UIBarButtonItemStyle.Plain, target: self, action: #selector(ContainerViewController.goBack))
let navController = UINavigationController(rootViewController: vc)
dispatch_async(dispatch_get_main_queue(),{
self.presentViewController(navController, animated: true, completion: nil)
})
i'm having no problem with the view controller navigation.
i need to set the value for DestinationLocation, so i need to get the indexPath for button clicked cell.
Please anybody help me out.
There is a simple way to make this work:
In your cellForRowAtIndexPath method, you can set a tag to your button with the current row value like:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = //cell
cell.myButton.tag = indexPath.row
cell.myButton.addTarget(self, action: #selector(ViewController.getDirection(_:)), forControlEvents: UIControlEvents.TouchUpInside)
}
and on your function
func getDirection(sender: UIButton) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: mapvc2 = storyboard.instantiateViewControllerWithIdentifier("mapvc2") as! mapvc2
let indexPathRow = sender.tag
let DestinationLocation = CLLocationCoordinate2D(latitude: val[indexPathRow].latlng[0] as! Double, longitude: val[indexPathRow].latlng[1] as! Double)
vc.PlotRoute(DestinationLocation)
vc.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "< back", style: UIBarButtonItemStyle.Plain, target: self, action: #selector(ContainerViewController.goBack))
let navController = UINavigationController(rootViewController: vc)
dispatch_async(dispatch_get_main_queue(),{
self.presentViewController(navController, animated: true, completion: nil)
})
}
It is result you expect ?