addTarget to button action within nib of collection view - ios

I have a keyboard extension in iOS 11 that includes a collection view of articles coming in from JSON. I have a button in the prototype cell that I would like to allow a user to press to open the article in Safari external to the keyboard. I can get it to open all links in a static URL, but I cant get it to open each article's URL. What am I missing?
I've put an example of the working simple static action and also included what I have tried but doesn't work in this code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(collectionView == self.key.colImages)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gifCollectionViewCell", for: indexPath) as! gifCollectionViewCell
cell.lblTitle.text = self.articles[indexPath.row].headline
let prefix: String = "https://res.cloudinary.com/djvbbwrnm/image/fetch/"
let options: String = "w_0.2/"
if let imageURL = self.articles[indexPath.row].imageURL
{
let articleURL = self.articles[indexPath.row].url
let url = URL(string: articleURL!)
let urlAppended = prefix+options+imageURL
cell.imgView.sd_setImage(with: URL(string: urlAppended), completed: nil)
//This works
cell.shareButton.addTarget(self, action: #selector(openLink), for: .touchUpInside)
//This doesn't
cell.shareButton.addTarget(self, action: #selector(openUrl(url: url)), for: .touchUpInside)
}
return cell
}
else
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCollectionViewCell", for: indexPath) as! catCollectionViewCell
cell.imgView.image = buttPics[indexPath.row]
cell.imgView.layer.cornerRadius = 2
cell.imgView.layer.masksToBounds = true
return cell
}
}
#objc func openLink(){
let articleURL = "http://google.com"
let url = URL(string: articleURL)
openUrl(url: url)
}
#objc func openUrl(url: URL?) {
let selector = sel_registerName("openURL:")
var responder = self as UIResponder?
while let r = responder, !r.responds(to: selector) {
responder = r.next
}
_ = responder?.perform(selector, with: url)
}

You cant add any other DataTypes as arguments. Because, you are adding addTarget for UIButton.
#objc func openLink(){
}
#objc func openLink(sender: UIButton){ // URL is not possible.
}
The above two codes are same. In second one, you can access that UIButton's property.
Runnable Code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(collectionView == self.key.colImages)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gifCollectionViewCell", for: indexPath) as! gifCollectionViewCell
cell.lblTitle.text = self.articles[indexPath.row].headline
let prefix: String = "https://res.cloudinary.com/djvbbwrnm/image/fetch/"
let options: String = "w_0.2/"
if let imageURL = self.articles[indexPath.row].imageURL
{
//let articleURL = self.articles[indexPath.row].url
//let url = URL(string: articleURL!)
let urlAppended = prefix+options+imageURL
cell.imgView.sd_setImage(with: URL(string: urlAppended), completed: nil)
//This works
cell.shareButton.addTarget(self, action: #selector(openLink), for: .touchUpInside)
//This doesn't
//cell.shareButton.addTarget(self, action: #selector(openUrl(url: url)), for: .touchUpInside)
cell.shareButton.tag = indexPath.row // SET TAG TO UIBUTTON
}
return cell
}
else
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCollectionViewCell", for: indexPath) as! catCollectionViewCell
cell.imgView.image = buttPics[indexPath.row]
cell.imgView.layer.cornerRadius = 2
cell.imgView.layer.masksToBounds = true
return cell
}
}
#objc func openLink(sender: UIButton){ // USE THIS.
let buttonTag : Int = sender.tag
let articleURL = self.articles[buttonTag].url
let url = URL(string: articleURL!)
// You can achieve by this way.
// Since I am in a keyboard extension, I added the follwoing code and it is working now.
let selector = sel_registerName("openURL:")
var responder = self as UIResponder?
while let r = responder, !r.responds(to: selector) {
responder = r.next
}
_ = responder?.perform(selector, with: url)
}

Related

How to display ad in uitableview admob swift?

I am very new to admob nativeads and I checked every tutorial
online on how to add ads in tableview, but I did not find any luck
This is the code I have so far in my cellforrowat function:
let cell = Bundle.main.loadNibNamed("NativeAdTableViewCell", owner: self, options: nil)?.first as! NativeAdTableViewCell
print("ad = \(ad == nil)")
cell.adTitle.text = ad?.headline
cell.adSubtitle.text = ad?.body
cell.img.image = ad?.icon?.image
cell.selectionStyle = UITableViewCell.SelectionStyle.none
return cell
Other code:
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADUnifiedNativeAd) {
nativeAd.delegate = self
ad = nativeAd
}
Thank you in advanced
You can simply add an empty element in your tableView dataSource array when receive your add like below:
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADUnifiedNativeAd {
nativeAd.delegate = self
self.yourDataSource.insert("", at: 0) // add empty element in where you show your ads
ad = nativeAd
yourTableView.reloadData()
}
After that your tableViewCell look like :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = self.yourDataSource[indexPath.row]as? [String]
if data == nil {
let nativeAd = self.ad //Your native ads object
nativeAd.rootViewController = self
let cell = tableView.dequeueReusableCell(withIdentifier: "nativecell", for: indexPath)as! nativecell // nativecell is your native ad cell
let adView = cell.adview
adView!.nativeAd = nativeAd
cell.headline.text = nativeAd.headline
cell.icon.image = nativeAd.icon?.image
cell.body.text = nativeAd.body
cell.action.isUserInteractionEnabled = false
cell.action.setTitle(nativeAd.callToAction, for: UIControl.State.normal)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "regularCell", for: indexPath)as! regularCell
return cell
}
}
Thanks

How to access album of photos for each row in UITableView

I have a UITableView with few rows. When I hold on a cell the Camera pop-up and I can take photos and to store them in an album of photos.
Each row can have an album of photos. The problem is that when I click on an album, then every time will open me the album with the last picture made and I don't know how to fix this issue with the indexPath.
Here is my code:
class CustomImg: UIImageView {
var indexPath: IndexPath?
}
class ChecklistVC: UIViewController {
lazy var itemSections: [ChecklistItemSection] = {
return ChecklistItemSection.checklistItemSections()
}()
var lastIndexPath: IndexPath!
var currentIndexPath: IndexPath!
...
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Constants.checklistCell, for: indexPath) as! ChecklistCell
let itemCategory = itemSections[indexPath.section]
let item = itemCategory.checklistItems[indexPath.row]
if item.imagesPath!.isEmpty{
cell.defectImageHeightConstraint.constant = 0
}
else{
let thumbnailImage = loadImageFromDiskWith(fileName: item.imagesPath?.last ?? String())
cell.defectImageView.indexPath = indexPath
cell.defectImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapOnDefectImageView(_:))))
cell.defectImageHeightConstraint.constant = 100
cell.defectImageView.isUserInteractionEnabled = true
cell.defectImageView.image = thumbnailImage
print("For section \(indexPath.section + 1) - row \(String(describing: indexPath.row + 1)) the album photos are: \(String(describing: item.imagesPath))")
}
return cell
}
#objc func tapOnDefectImageView(_ sender: UITapGestureRecognizer){
guard let img = sender.view as? CustomImg, let indexPath = img.indexPath else { return }
currentIndexPath = indexPath
let listImagesDefectVC = storyboard?.instantiateViewController(withIdentifier: "ListImagesDefectID") as! ListImagesDefectVC
let item = itemSections[indexPath.section].checklistItems[indexPath.row]
listImagesDefectVC.listImagesPath = item.imagesPath
listImagesDefectVC.isPhotoAccessedFromChecklist = true
listImagesDefectVC.delegate = self
navigationController?.pushViewController(listImagesDefectVC, animated: true)
}
// A menu from where the user can choose to take pictures for "Vehicle Damage/Defects" or "Trailer Damage/Defects"
func showOptionsForAddPhoto(_ indexPath: IndexPath){
let addPhotoForVehicle = UIAlertAction(title: "Add photo for Vehicle", style: .default) { action in
self.lastIndexPath = indexPath // Get the position of the cell where to add the vehicle photo
self.showCamera(imagePicker: self.imagePicker)
}
let addPhotoForTrailer = UIAlertAction(title: "Add photo for Trailer", style: .default) { action in
self.lastIndexPath = indexPath
self.showCamera(imagePicker: self.imagePicker)
}
let actionSheet = configureActionSheet()
actionSheet.addAction(addPhotoForVehicle)
actionSheet.addAction(addPhotoForTrailer)
self.present(actionSheet, animated: true, completion: nil)
}
// Get the list of the images from ListImagesDefectVC
extension ChecklistVC: ListImagesDefectDelegate {
func receiveListImagesUpdated(imagesFromList: [String]?) {
print("Received Array: \(imagesFromList ?? [])")
let item = itemSections[currentIndexPath.section].checklistItems[currentIndexPath.row]
item.imagesPath = imagesFromList
}
}
}
Here is a GIF with my actual issue. In this capture I click only on Photo 1 and Photo 3. And every time Photo 2 take the value of what I clicked before:
http://g.recordit.co/VMeGZbf7TF.gif
Thank you if you are reading this.
I guess in tapOnDefectImageView you should use the clicked indexPath for the cell not lastIndexPath which is the reason why clicking a row shows photos of last clicked indexPath
so either add this gesture inside the cell and in the action method do
delegate?.tapOnDefectImageView(self) //// self = cell
and use
#objc func tapOnDefectImageView(_ gest:ChecklistCell){
guard let indexPath = tableView.indexPath(cell) else { return }
let listImagesDefectVC = storyboard?.instantiateViewController(withIdentifier: "ListImagesDefectID") as! ListImagesDefectVC
let item = itemSections[indexPath.section].checklistItems[indexPath.row]
listImagesDefectVC.listImagesPath = item.imagesPath
listImagesDefectVC.isPhotoAccessedFromChecklist = true
listImagesDefectVC.delegate = self
navigationController?.pushViewController(listImagesDefectVC, animated: true)
}
or create
class CustomImg:UIImageView {
var indexPath:IndexPath?
}
with this inside cellForRowAt
cell.defectImageView.indexPath = indexPath
cell.defectImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapOnDefectImageView)))
then assign the class to the imageView of the cell and now you can do
#objc func tapOnDefectImageView(_ sender:UITapGestureRecognizer){
guard let img = sender.view as? CustomImg , let indexPath = img.indexPath else { return }
let listImagesDefectVC = storyboard?.instantiateViewController(withIdentifier: "ListImagesDefectID") as! ListImagesDefectVC
let item = itemSections[indexPath.section].checklistItems[indexPath.row]
listImagesDefectVC.listImagesPath = item.imagesPath
listImagesDefectVC.isPhotoAccessedFromChecklist = true
listImagesDefectVC.delegate = self
navigationController?.pushViewController(listImagesDefectVC, animated: true)
}

UITableView when click button and change its image Swift 4

I have a problem when I click a button in UITableView.
When I click a button to change its image, another indexpath changes it image.
Two buttons change their image when I click one button.
This video is shown the problem :
Video Shown Problem
This is my code to button click :
#objc func btnAction(_ sender: UIButton) {
let section = 0
let row = sender.tag
let indexPath = IndexPath(row: row, section: section)
//let point = sender.convert(CGPoint.zero, to: servicestable as UIView)
// let indexPath: IndexPath! = servicestable.indexPathForRow(at: point)
let cell: ServicesCell = self.servicestable.cellForRow(at: indexPath) as! ServicesCell
print(sender.tag)
if services[indexPath.row].choose == "0" {
cell.check.setImage(UIImage(named: "Rectangle1"), for: .normal)
services[indexPath.row].choose = "1"
// checkchoose.updateValue(1, forKey: indexPath.row)
print("number: " + String(indexPath.row))
chooseservicesw.updateValue("0", forKey: String(indexPath.row+1))
// servicestable.reloadData()
}
else if services[indexPath.row].choose == "1" {
cell.check.setImage(UIImage(named: "checkbox1"), for: .normal)
services[indexPath.row].choose = "0"
// checkchoose.updateValue(0, forKey: indexPath.row)
print("number: " + String(indexPath.row))
chooseservicesw.removeValue(forKey: String(indexPath.row+1))
// servicestable.reloadData()
} }
and this is my code to Cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:ServicesCell = servicestable.dequeueReusableCell(withIdentifier: "servicess", for: indexPath) as! ServicesCell
cell.name.text = services[indexPath.row].name
let photo = services[indexPath.row].icon
let imgURL = MainUrl + photo! // or jpg
// cell.icon.setImageFromURl(stringImageUrl: imgURL)
let url = URL(string: imgURL)
cell.icon.kf.setImage(with: url)
// print(checkchoose[indexPath.row])
print(services[indexPath.row].choose)
cell.check.tag = indexPath.row
cell.check.addTarget(self, action: #selector(self.btnAction(_:)), for: .touchUpInside)
cell.checkpay.tag = indexPath.row
cell.checkpay.addTarget(self, action: #selector(self.btnAction2(_:)), for: .touchUpInside)
cell.price.addTarget(self, action: #selector(self.endedit(_:)), for: .editingDidEnd)
cell.price.addTarget(self, action: #selector(self.endedit(_:)), for: .editingChanged)
cell.selectionStyle = .none
return cell
}
When you perform dequeueReusableCell, iOS not generates your array's counts of cells. Instead of this, it generates fewer cells and use these cells again and again on scrolls. In your case selected shown cells are actually same cells.
The solution: You should store the selection flag for your cells in your datasource array (in your case its services). And in cellForRowAt method you should select or deselect checkboxes according to your stored selection data.
Edit:
if services[indexPath.row].choose == "0" {
cell.check.setImage(UIImage(named: "Rectangle1"), for: .normal)
services[indexPath.row].choose = "1"
chooseservicesw.updateValue("0", forKey: String(indexPath.row+1))
} else if services[indexPath.row].choose == "1" {
cell.check.setImage(UIImage(named: "checkbox1"), for: .normal)
services[indexPath.row].choose = "0"
chooseservicesw.removeValue(forKey: String(indexPath.row+1))
}
Add this part to end of your cellForRowAt method.
This problem is because reuseable cell in Memory you can handle like that:
In Your ViewController or TableViewController add Array that handel selected Model selectedIndexPathServices Add your YourServiceModel
Example
class TableViewController: UITableViewController {
var selectedIndexPathServices :[YourServiceModel] = [YourServiceModel]()
override func viewDidLoad() {
super.viewDidLoad()
}
}
At your btnAction
#objc func btnAction(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ServicesCell else { return}
let indexPath = self.tableView.indexPath(for: cell)
if selectedIndexPathServices.contains(services[indexPath.row]){
selectedIndexPathServices.remove(at: indexPath.row)
}else{
selectedIndexPathServices.append(services[indexPath.row])
}
}
At your cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:ServicesCell = servicestable.dequeueReusableCell(withIdentifier: "servicess", for: indexPath) as! ServicesCell
cell.name.text = services[indexPath.row].name
let photo = services[indexPath.row].icon
let imgURL = MainUrl + photo! // or jpg
// cell.icon.setImageFromURl(stringImageUrl: imgURL)
let url = URL(string: imgURL)
cell.icon.kf.setImage(with: url)
// print(checkchoose[indexPath.row])
if selectedIndexPathServices.contains(services[indexPath.row]){
if services[indexPath.row].choose == "0" {
cell.check.setImage(UIImage(named: "Rectangle1"), for: .normal)
services[indexPath.row].choose = "1"
chooseservicesw.updateValue("0", forKey: String(indexPath.row+1))
}
else if services[indexPath.row].choose == "1" {
cell.check.setImage(UIImage(named: "checkbox1"), for: .normal)
services[indexPath.row].choose = "0"
chooseservicesw.removeValue(forKey: String(indexPath.row+1))
}
}
cell.check.tag = indexPath.row
cell.check.addTarget(self, action: #selector(self.btnAction(_:)), for: .touchUpInside)
cell.checkpay.tag = indexPath.row
cell.checkpay.addTarget(self, action: #selector(self.btnAction2(_:)), for: .touchUpInside)
cell.price.addTarget(self, action: #selector(self.endedit(_:)), for: .editingDidEnd)
cell.price.addTarget(self, action: #selector(self.endedit(_:)), for: .editingChanged)
cell.selectionStyle = .none
return cell
}

issue of reuse collection view cell

I have stuck on this issue for 2 days. I don't know why.
I have a collection view for shopping cart purpose. there is a stepper on the reusable cell with a valueChanged event.
I use the following code to handle the problem:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! CartCollectionViewCell
cell.name?.text = cart[indexPath.row].value[0]
let url = URL(string: (baseUrl + (cart[indexPath.row].value[1])))
cell.img.setImageWith(url!)
cell.price?.text = "$" + (cart[indexPath.row].value[2])
cell.remove.addTarget(self, action: #selector(cartViewController.remove_from_cart(sender:)), for: .touchDown)
cell.remove.tag = indexPath.row
cell.qty.value = Double(cart[indexPath.row].value[3])!
cell.qty.addTarget(self, action: #selector(cartViewController.stepperValueChanged(stepper:)), for: .valueChanged)
cell.qty.tag = indexPath.row
return cell
}
func stepperValueChanged(stepper: GMStepper) {
let pos = stepper.convert(CGPoint.zero, to: collection)
let indexPath = collection.indexPathForItem(at: pos)!
let cell: CartCollectionViewCell = collection.cellForItem(at: indexPath) as! CartCollectionViewCell
if cell.qty != nil {
let value = String(Int(cell.qty.value))
var item = cart[indexPath.row]
item.value[3] = value
cart[indexPath.row] = item
}
}
func remove_from_cart(sender: UIButton){
let pos = sender.convert(CGPoint.zero, to: collection)
let indexPath = collection.indexPathForItem(at: pos)!
cart.remove(at: indexPath.row)
amount = 0.0
collection.reloadData()
}
after I scroll the collection view following error occurs:
"fatal error: unexpectedly found nil while unwrapping an Optional value"
let cell: CartCollectionViewCell = collection.cellForItem(at: indexPath) as! CartCollectionViewCell
this line is highlighted by Xcode.

How to get multiple buttons from a single tableViewcell?

I am making a quiz in a tableView that has 4 Buttons (options), I tagged them on a story board like 201,202,203,204 and got all of them successfully in tableView methods. But after adding targets to buttons, I am not able to get particular buttons in buttonClicked method.
func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 }
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return questions.count }
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
(cell.viewWithTag(100) as! UILabel).text = "Q : " + (questions[indexPath.row].objectForKey("MocQuestion")! as? String)!
(cell.viewWithTag(100) as! UILabel).font = themeFont
(cell.viewWithTag(101) as! UILabel).text = questions[indexPath.row].objectForKey("Op1")! as? String
(cell.viewWithTag(102) as! UILabel).text = questions[indexPath.row].objectForKey("Op2")! as? String
(cell.viewWithTag(103) as! UILabel).text = questions[indexPath.row].objectForKey("Op3")! as? String
(cell.viewWithTag(104) as! UILabel).text = questions[indexPath.row].objectForKey("Op4")! as? String
let btn1 = (cell.viewWithTag(201) as! UIButton)
let btn2 = (cell.viewWithTag(202) as! UIButton)
let btn3 = (cell.viewWithTag(203) as! UIButton)
let btn4 = (cell.viewWithTag(204) as! UIButton)
// btn1.tag = indexPath.row * 100 + 0
// btn1.tag = indexPath.row * 100 + 1
// btn1.tag = indexPath.row * 100 + 2
// btn1.tag = indexPath.row * 100 + 3
btn1.addTarget(self, action: #selector(Quiz.buttonClicked(_:)),forControlEvents: UIControlEvents.TouchUpInside)
btn2.addTarget(self, action: #selector(Quiz.buttonClicked(_:)),forControlEvents: UIControlEvents.TouchUpInside)
btn3.addTarget(self, action: #selector(Quiz.buttonClicked(_:)),forControlEvents: UIControlEvents.TouchUpInside)
btn4.addTarget(self, action: #selector(Quiz.buttonClicked(_:)),forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func buttonClicked(sender:UIButton)
{
let tag = sender.tag
print(tag)
}
If you want the indexPath to access the questions Array then you can try like this.
func buttonClicked(sender:UIButton) {
let center = sender.center
let point = sender.superview!.convertPoint(center, toView:self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(point)
//Now you have tag of button check for that
if (sender.tag == 201) {
print("Option A")
}
else if (sender.tag == 202) {
print("Option B")
}
else if (sender.tag == 203) {
print("Option C")
}
else {
print("Option D")
}
print(question[indexPath.row])
}
In swift 3 You can try bellow like
My table cell class
class Custom_Cell: UITableViewCell {
#IBOutlet weak var ButtonA: UIButton!
#IBOutlet weak var ButtonB: UIButton!
#IBOutlet weak var ButtonC: UIButton!
}
Set Tag in table view
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Custom_Cell;
cell.ButtonA.tag = indexPath.row;
cell.ButtonB.tag = indexPath.row;
cell.ButtonA.tag = indexPath.row;
//Add Action Methods to UIButtons
cell.ButtonA.addTarget(self, action: #selector(ButtonAAction), for: .touchUpInside)
cell.ButtonB.addTarget(self, action: #selector(ButtonBAction), for: .touchUpInside)
cell.ButtonA.addTarget(self, action: #selector(ButtonCAction), for: .touchUpInside)
return cell;
}
Button Action will look like ..
func ButtonAAction(_ sender: Any) {
//Get Button cell position.
let ButtonPosition = (sender as AnyObject).convert(CGPoint.zero, to: tableView)
let indexPath = tableView.indexPathForRow(at: ButtonPosition)
if indexPath != nil {
print("Cell indexPath: \(indexPath?.row)")
}
}
In order to get every ques separate button click event you can pass the unique ID as postfix or prefix e.g 20101 or 01201 of every ques as a tag of button instead of hard-coded. Then get the tag and extract ques id first now proceed for examination as per ques.

Resources