จะรับ indexpath.row ได้อย่างไรเมื่อเปิดใช้งานองค์ประกอบ


107

ฉันมี tableview พร้อมปุ่มและฉันต้องการใช้ indexpath.row เมื่อมีการแตะปุ่มใดปุ่มหนึ่ง นี่คือสิ่งที่ฉันมีอยู่ในตอนนี้ แต่จะเป็น 0 เสมอ

var point = Int()
func buttonPressed(sender: AnyObject) {
    let pointInTable: CGPoint =         sender.convertPoint(sender.bounds.origin, toView: self.tableView)
    let cellIndexPath = self.tableView.indexPathForRowAtPoint(pointInTable)
    println(cellIndexPath)
    point = cellIndexPath!.row
    println(point)
}

ฉันควรใช้ IndexPathForSelectedRow () แทนตัวแปร point หรือไม่ หรือควรใช้ที่ไหน
Vincent

คำตอบ:


167

giorashc เกือบจะได้คำตอบแล้ว แต่เขามองข้ามความจริงที่ว่าเซลล์มีcontentViewชั้นพิเศษ ดังนั้นเราต้องลึกลงไปอีกหนึ่งชั้น:

guard let cell = sender.superview?.superview as? YourCellClassHere else {
    return // or fatalError() or whatever
}

let indexPath = itemTable.indexPath(for: cell)

เนื่องจากภายในลำดับชั้นของมุมมอง tableView มีเซลล์เป็นมุมมองย่อยซึ่งต่อมามี 'มุมมองเนื้อหา' เป็นของตัวเองนี่คือเหตุผลที่คุณต้องได้รับ superview ของมุมมองเนื้อหานี้เพื่อรับเซลล์นั้นเอง ด้วยเหตุนี้หากปุ่มของคุณอยู่ในมุมมองย่อยแทนที่จะอยู่ในมุมมองเนื้อหาของเซลล์โดยตรงคุณจะต้องเจาะลึกลงไปอีกหลายชั้นเพื่อเข้าถึง

ข้างต้นเป็นแนวทางหนึ่ง แต่ไม่จำเป็นต้องเป็นแนวทางที่ดีที่สุด แม้ว่าจะใช้งานได้ แต่จะถือว่ารายละเอียดเกี่ยวกับไฟล์UITableViewCellที่ Apple ไม่จำเป็นต้องจัดทำเป็นเอกสารเช่นลำดับชั้นของมุมมอง สิ่งนี้อาจเปลี่ยนแปลงได้ในอนาคตและรหัสข้างต้นอาจทำงานได้ดีโดยไม่สามารถคาดเดาได้

จากผลข้างต้นด้วยเหตุผลด้านอายุการใช้งานที่ยาวนานและความน่าเชื่อถือฉันขอแนะนำให้ใช้แนวทางอื่น มีทางเลือกมากมายที่ระบุไว้ในชุดข้อความนี้และฉันขอแนะนำให้คุณอ่าน แต่รายการโปรดส่วนตัวของฉันมีดังนี้:

ถือคุณสมบัติของการปิดในคลาสเซลล์ของคุณให้วิธีการดำเนินการของปุ่มเรียกใช้สิ่งนี้

class MyCell: UITableViewCell {
    var button: UIButton!

    var buttonAction: ((Any) -> Void)?

    @objc func buttonPressed(sender: Any) {
        self.buttonAction?(sender)
    }
}

จากนั้นเมื่อคุณสร้างเซลล์cellForRowAtIndexPathคุณสามารถกำหนดมูลค่าให้กับการปิดของคุณได้

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! MyCell
    cell.buttonAction = { sender in
        // Do whatever you want from your button here.
    }
    // OR
    cell.buttonAction = buttonPressed(closure: buttonAction, indexPath: indexPath) // <- Method on the view controller to handle button presses.
}

ด้วยการย้ายโค้ดตัวจัดการของคุณมาที่นี่คุณสามารถใช้ประโยชน์จากindexPathอาร์กิวเมนต์ที่มีอยู่แล้ว นี่เป็นวิธีการที่ปลอดภัยกว่ามากจากวิธีการที่ระบุไว้ข้างต้นเนื่องจากไม่ได้อาศัยลักษณะที่ไม่มีเอกสาร


2
เห็นดี ฉันเป็นนักพัฒนาที่มีความสามารถฉันสัญญา;) - ได้แก้ไขคำตอบของฉันแล้ว
Jacob King

12
นี่ไม่ใช่วิธีที่เหมาะสมในการดึงเซลล์จากปุ่ม เค้าโครงของเซลล์มีการเปลี่ยนแปลงในช่วงหลายปีที่ผ่านมาและรหัสเช่นนี้จะไม่ทำงานเมื่อเกิดเหตุการณ์ดังกล่าว อย่าใช้แนวทางนี้
rmaddy

11
นี่เป็นทางออกที่ไม่ดี ถือว่ารายละเอียดเกี่ยวกับ UITableViewCells ที่ Apple ไม่เคยเห็นด้วย แม้ว่า UITableViewCells จะมีคุณสมบัติ contentView แต่ก็ไม่มีการรับประกันว่า superview ของ contentView จะเป็นเซลล์เสมอไป
bpapa

1
@PintuRajput คุณช่วยอธิบายลำดับชั้นการดูให้ฉันฟังหน่อยได้ไหม คุณน่าจะเห็นสิ่งนี้เนื่องจากปุ่มของคุณไม่ใช่มุมมองย่อยโดยตรงของมุมมองเนื้อหาของเซลล์
Jacob King

2
@ymutlu ฉันเห็นด้วยอย่างยิ่งฉันได้ระบุสิ่งนี้ในคำตอบ ฉันยังเสนอวิธีแก้ปัญหาที่มีประสิทธิภาพมากขึ้น เหตุผลที่ฉันทิ้งต้นฉบับไว้เพราะฉันรู้สึกว่ามันดีกว่าที่จะแสดงให้นักพัฒนาคนอื่นเห็นถึงปัญหาด้วยวิธีการมากกว่าที่จะหลีกเลี่ยงมันโดยสิ้นเชิงสิ่งนี้จะสอนพวกเขาในสิ่งที่คุณไม่เห็น :)
Jacob King

61

แนวทางของฉันในการแก้ปัญหาประเภทนี้คือการใช้โปรโตคอลตัวแทนระหว่างเซลล์และมุมมองตาราง สิ่งนี้ช่วยให้คุณเก็บตัวจัดการปุ่มไว้ในคลาสย่อยของเซลล์ซึ่งช่วยให้คุณกำหนดตัวจัดการการดำเนินการแบบสัมผัสให้กับเซลล์ต้นแบบในตัวสร้างอินเทอร์เฟซในขณะที่ยังคงรักษาตรรกะของตัวจัดการปุ่มไว้ในตัวควบคุมมุมมอง

นอกจากนี้ยังหลีกเลี่ยงวิธีการที่อาจเปราะบางในการนำทางตามลำดับชั้นของมุมมองหรือการใช้tagคุณสมบัติซึ่งมีปัญหาเมื่อดัชนีเซลล์เปลี่ยนแปลง (อันเป็นผลมาจากการแทรกการลบหรือการเรียงลำดับใหม่)

CellSubclass.swift

protocol CellSubclassDelegate: class {
    func buttonTapped(cell: CellSubclass)
}

class CellSubclass: UITableViewCell {

@IBOutlet var someButton: UIButton!

weak var delegate: CellSubclassDelegate?

override func prepareForReuse() {
    super.prepareForReuse()
    self.delegate = nil
}

@IBAction func someButtonTapped(sender: UIButton) {
    self.delegate?.buttonTapped(self)
}

ViewController.swift

class MyViewController: UIViewController, CellSubclassDelegate {

    @IBOutlet var tableview: UITableView!

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CellSubclass

        cell.delegate = self

        // Other cell setup

    } 

    //  MARK: CellSubclassDelegate

    func buttonTapped(cell: CellSubclass) {
        guard let indexPath = self.tableView.indexPathForCell(cell) else {
            // Note, this shouldn't happen - how did the user tap on a button that wasn't on screen?
            return
        }

        //  Do whatever you need to do with the indexPath

        print("Button tapped on row \(indexPath.row)")
    }
} 

buttonTappedเป็นฟังก์ชันมอบหมายและอยู่ในตัวควบคุมมุมมอง ในตัวอย่างของฉันsomeButtonTappedคือวิธีการดำเนินการในเซลล์
Paulw11

@ paulw11 ฉันได้รับเซลล์ไม่มีปุ่มสมาชิกแตะในวิธีนี้@IBAction func someButtonTapped(sender: UIButton) { self.delegate?.buttonTapped(self) }
EI Captain v2.0

1
นี่เป็นวิธีแก้ปัญหาที่ค่อนข้างดี (ไม่มีที่ไหนใกล้แย่เท่ากับทั้งสองที่มีคะแนนโหวตมากกว่าโดยใช้แท็กที่ดู superview) แต่รู้สึกว่าต้องเพิ่มโค้ดพิเศษมากเกินไป
bpapa

2
นี่เป็นวิธีแก้ปัญหาที่ถูกต้องและควรเป็นคำตอบที่ได้รับการยอมรับ ไม่ใช้คุณสมบัติของแท็กในทางที่ผิดไม่ถือว่าการสร้างเซลล์ (ซึ่ง Apple สามารถเปลี่ยนแปลงได้ง่าย) และจะยังคงใช้งานได้ (โดยไม่มีการเข้ารหัสเพิ่มเติม) เมื่อเซลล์ถูกย้ายหรือเพิ่มเซลล์ใหม่ระหว่างเซลล์ที่มีอยู่
Robotic Cat

1
@ Paulw11 ตอนแรกฉันคิดว่านี่เป็นรหัสจำนวนมาก แต่ได้รับการพิสูจน์แล้วว่ามีความยืดหยุ่นมากกว่าที่ฉันเคยใช้มาก่อน ขอบคุณสำหรับการโพสต์วิธีแก้ปัญหาที่มีประสิทธิภาพนี้
เอเดรียน

54

UPDATE : การรับ indexPath ของเซลล์ที่มีปุ่ม (ทั้งส่วนและแถว):

การใช้ตำแหน่งปุ่ม

ภายในbuttonTappedวิธีการของคุณคุณสามารถจับตำแหน่งของปุ่มแปลงเป็นพิกัดใน tableView จากนั้นรับ indexPath ของแถวที่พิกัดนั้น

func buttonTapped(_ sender:AnyObject) {
    let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:self.tableView)
    let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
}

หมายเหตุ : บางครั้งคุณสามารถใช้ฟังก์ชัน edge ได้เมื่อใช้ฟังก์ชันview.convert(CGPointZero, to:self.tableView)ในการค้นหาnilแถว ณ จุดหนึ่งแม้ว่าจะมีเซลล์ tableView อยู่ก็ตาม ในการแก้ไขปัญหานี้ให้ลองส่งพิกัดจริงที่หักล้างจากจุดเริ่มต้นเล็กน้อยเช่น:

let buttonPosition:CGPoint = sender.convert(CGPoint.init(x: 5.0, y: 5.0), to:self.tableView)

คำตอบก่อนหน้า: การใช้คุณสมบัติแท็ก (ส่งคืนเฉพาะแถว)

แทนที่จะปีนขึ้นไปบนต้นไม้ superview เพื่อคว้าตัวชี้ไปยังเซลล์ที่เก็บ UIButton มีเทคนิคที่ปลอดภัยและทำซ้ำได้มากขึ้นโดยใช้คุณสมบัติ button.tag ที่กล่าวถึงโดย Antonio ข้างต้นซึ่งอธิบายไว้ในคำตอบนี้และแสดงไว้ด้านล่าง:

ในการcellForRowAtIndexPath:ตั้งค่าคุณสมบัติแท็ก:

button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)

จากนั้นในbuttonClicked:ฟังก์ชั่นคุณอ้างอิงแท็กนั้นเพื่อจับแถวของ indexPath ที่ปุ่มนั้นอยู่:

func buttonClicked(sender:UIButton) {
    let buttonRow = sender.tag
}

ฉันชอบวิธีนี้เนื่องจากพบว่าการแกว่งไปมาบนต้นไม้ซุปเปอร์วิวอาจเป็นวิธีที่เสี่ยงในการออกแบบแอป นอกจากนี้สำหรับวัตถุประสงค์ -C ฉันเคยใช้เทคนิคนี้ในอดีตและมีความสุขกับผลลัพธ์


5
นี่เป็นวิธีที่ดีในการทำเช่นนี้และฉันจะโหวตเพิ่มเพื่อให้ตัวแทนของคุณเริ่มต้นขึ้นเล็กน้อยอย่างไรก็ตามข้อบกพร่องเพียงอย่างเดียวคือไม่ให้การเข้าถึง indexPath.section หากจำเป็น ตอบดีมาก!
Jacob King

ขอบคุณเจคอบ! ขอขอบคุณตัวแทนกรรม หากคุณต้องการที่จะได้รับindexPath.sectionนอกเหนือไปindexPath.row(โดยไม่ต้องรีเซ็ตคุณสมบัติแท็กที่indexPath.section) ในcellForRowAtIndexPath:คุณก็สามารถเปลี่ยนแท็กbutton.tag = indexPathและจากนั้นในbuttonClicked:ฟังก์ชั่นที่คุณสามารถเข้าถึงทั้งโดยการใช้และsender.tag.row sender.tag.section
Iron John Bonney

1
นี่เป็นคุณสมบัติใหม่หรือไม่เพราะฉันแน่ใจว่าฉันจำคุณสมบัติของแท็กที่เป็นประเภท Int ไม่ได้พิมพ์ AnyObject เว้นแต่จะมีการเปลี่ยนแปลงใน swift 2.3
Jacob King

@JacobKing คุณพูดถูก! ฉันไม่ดีฉันเว้นระยะห่างโดยสิ้นเชิงเมื่อเขียนความคิดเห็นนั้นและคิดว่าแท็กเป็นประเภท AnyObject Derp - อย่ารังเกียจฉัน มันจะมีประโยชน์ถ้าคุณสามารถส่ง indexPath เป็นแท็กแม้ว่า ...
Iron John Bonney

3
ไม่ใช่แนวทางที่ดีเช่นกัน ประการหนึ่งมันจะใช้ได้เฉพาะในมุมมองตารางที่มีส่วนเดียว
bpapa

16

ใช้ส่วนขยายเพื่อ UITableView เพื่อดึงเซลล์สำหรับมุมมองใด ๆ :


คำตอบของ @ Paulw11 ในการตั้งค่าประเภทเซลล์ที่กำหนดเองด้วยคุณสมบัติตัวแทนที่ส่งข้อความไปยังมุมมองตารางเป็นวิธีที่ดี แต่ต้องใช้งานจำนวนหนึ่งในการตั้งค่า

ฉันคิดว่าการเดินตามลำดับชั้นมุมมองของเซลล์มุมมองตารางเพื่อค้นหาเซลล์นั้นเป็นความคิดที่ไม่ดี มันเปราะบาง - หากคุณใส่ปุ่มของคุณในมุมมองในภายหลังเพื่อวัตถุประสงค์ในการจัดวางโค้ดนั้นมีแนวโน้มที่จะพัง

การใช้แท็กมุมมองก็เปราะบางเช่นกัน คุณต้องจำไว้ว่าต้องตั้งค่าแท็กเมื่อคุณสร้างเซลล์และหากคุณใช้แนวทางดังกล่าวในตัวควบคุมมุมมองที่ใช้แท็กมุมมองเพื่อจุดประสงค์อื่นคุณสามารถมีหมายเลขแท็กที่ซ้ำกันและรหัสของคุณอาจทำงานไม่ได้ตามที่คาดไว้

ฉันได้สร้างส่วนขยายให้กับ UITableView ที่ให้คุณได้รับ indexPath สำหรับมุมมองใด ๆ ที่มีอยู่ในเซลล์มุมมองตาราง จะส่งคืนค่าOptionalที่จะเป็นศูนย์หากมุมมองที่ส่งผ่านจริงไม่อยู่ในเซลล์มุมมองตาราง ด้านล่างนี้คือไฟล์ต้นฉบับส่วนขยายทั้งหมด คุณสามารถใส่ไฟล์นี้ในโปรเจ็กต์ของคุณจากนั้นใช้indexPathForView(_:)เมธอดรวมเพื่อค้นหา indexPath ที่มีมุมมองใด ๆ

//
//  UITableView+indexPathForView.swift
//  TableViewExtension
//
//  Created by Duncan Champney on 12/23/16.
//  Copyright © 2016-2017 Duncan Champney.
//  May be used freely in for any purpose as long as this 
//  copyright notice is included.

import UIKit

public extension UITableView {
  
  /**
  This method returns the indexPath of the cell that contains the specified view
   
   - Parameter view: The view to find.
   
   - Returns: The indexPath of the cell containing the view, or nil if it can't be found
   
  */
  
    func indexPathForView(_ view: UIView) -> IndexPath? {
        let center = view.center
        let viewCenter = self.convert(center, from: view.superview)
        let indexPath = self.indexPathForRow(at: viewCenter)
        return indexPath
    }
}

ในการใช้งานคุณสามารถเรียกใช้เมธอดใน IBAction สำหรับปุ่มที่มีอยู่ในเซลล์:

func buttonTapped(_ button: UIButton) {
  if let indexPath = self.tableView.indexPathForView(button) {
    print("Button tapped at indexPath \(indexPath)")
  }
  else {
    print("Button indexPath not found")
  }
}

(โปรดทราบว่าindexPathForView(_:)ฟังก์ชั่นนี้จะใช้งานได้ก็ต่อเมื่อออบเจ็กต์มุมมองที่ส่งผ่านนั้นมีอยู่ในเซลล์ที่กำลังอยู่บนหน้าจอนั่นสมเหตุสมผลเนื่องจากมุมมองที่ไม่ได้อยู่บนหน้าจอไม่ได้เป็นของ indexPath ที่เฉพาะเจาะจงจึงมีแนวโน้มที่จะ ถูกกำหนดให้กับ indexPath อื่นเมื่อมีการรีไซเคิลเซลล์)

แก้ไข:

คุณสามารถดาวน์โหลดโครงการสาธิตที่ใช้งานได้ซึ่งใช้ส่วนขยายข้างต้นจาก Github: TableViewExtension.git


ขอบคุณฉันใช้ส่วนขยายเพื่อรับ indexPath ของ textview ในเซลล์ - ทำงานได้อย่างสมบูรณ์
Jeremy Andrews

9

สำหรับ Swift2.1

ฉันพบวิธีที่จะทำมันหวังว่ามันจะช่วยได้

let point = tableView.convertPoint(CGPoint.zero, fromView: sender)

    guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
        fatalError("can't find point in tableView")
    }

หมายความว่าอย่างไรหากข้อผิดพลาดทำงาน มีสาเหตุอะไรบ้างที่ทำให้ไม่พบจุดใน tableView
OOProg

สิ่งนี้ (หรือคล้ายกันโดยใช้วิธีการแปลง UIView) ควรเป็นคำตอบที่ยอมรับได้ ไม่แน่ใจว่าทำไมถึงเป็นอันดับ 4 ในขณะนี้เนื่องจากไม่ได้ตั้งสมมติฐานเกี่ยวกับมรดกส่วนตัวของมุมมองตารางจึงไม่ใช้คุณสมบัติแท็ก (เกือบตลอดเวลาเป็นความคิดที่ไม่ดี) และไม่เกี่ยวข้องกับโค้ดพิเศษจำนวนมาก
bpapa

9

โซลูชัน Swift 4:

คุณมีปุ่ม (myButton) หรือมุมมองอื่น ๆ ในเซลล์ กำหนดแท็กใน cellForRowAt เช่นนี้

cell.myButton.tag = indexPath.row

ตอนนี้คุณแตะฟังก์ชันหรืออื่น ๆ ดึงออกมาในลักษณะนี้และบันทึกในตัวแปรภายใน

currentCellNumber = (sender.view?.tag)!

หลังจากนี้คุณสามารถใช้ที่ใดก็ได้ currentCellNumber นี้เพื่อรับ indexPath.row ของปุ่มที่เลือก

สนุก!


วิธีนี้ใช้ได้ผล แต่แท็กการดูมีความเปราะบางดังที่กล่าวไว้ในคำตอบของฉัน แท็กจำนวนเต็มอย่างง่ายจะใช้ไม่ได้กับมุมมองตารางแบบแบ่งส่วน (IndexPath เป็นจำนวนเต็ม 2 จำนวน) แนวทางของฉันจะใช้งานได้ตลอดเวลาและไม่จำเป็นต้องติดตั้งแท็กลงในปุ่ม (หรือมุมมองแบบแตะอื่น ๆ )
Duncan C

คุณสามารถจัดเก็บส่วนและแถว / รายการในจำนวนเต็มเดียว ดูคำตอบของฉัน ...
mramosch

6

ใน Swift 4 เพียงใช้สิ่งนี้:

func buttonTapped(_ sender: UIButton) {
        let buttonPostion = sender.convert(sender.bounds.origin, to: tableView)

        if let indexPath = tableView.indexPathForRow(at: buttonPostion) {
            let rowIndex =  indexPath.row
        }
}

คำตอบที่สะอาดที่สุดควรเป็นคำตอบที่เลือก สิ่งเดียวที่ควรทราบคือtableViewเป็นตัวแปรเต้าเสียบที่ต้องอ้างอิงก่อนที่คำตอบนี้จะทำงาน
10000RubyPools

ทำงานอย่างมีเสน่ห์ !!
Parthpatel1105

4

ง่ายมากในการรับเส้นทางดัชนีอย่างรวดเร็ว 4, 5

let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: "buttonTapped:", forControlEvents: 
UIControlEvents.TouchUpInside)

วิธีรับ IndexPath ภายใน Btn คลิก:

func buttonTapped(_ sender: UIButton) {
     print(sender.tag)  
}

3

เนื่องจากผู้ส่งตัวจัดการเหตุการณ์เป็นปุ่มเองฉันจึงใช้tagคุณสมบัติของปุ่มเพื่อจัดเก็บดัชนีเริ่มต้นในcellForRowAtIndexPathคุณสมบัติในการจัดเก็บดัชนีเริ่มต้นได้ใน

แต่ด้วยงานอีกเล็กน้อยฉันจะทำในลักษณะที่แตกต่างไปจากเดิมอย่างสิ้นเชิง หากคุณกำลังใช้เซลล์ที่กำหนดเองนี่คือวิธีที่ฉันจะแก้ไขปัญหา:

  • เพิ่มคุณสมบัติ "indexPath" ลงในเซลล์ตารางที่กำหนดเอง
  • เริ่มต้นใน cellForRowAtIndexPath
  • ย้ายตัวจัดการการแตะจากตัวควบคุมมุมมองไปยังการใช้งานเซลล์
  • ใช้รูปแบบการมอบหมายเพื่อแจ้งผู้ควบคุมมุมมองเกี่ยวกับเหตุการณ์การแตะโดยส่งผ่านเส้นทางดัชนี

อันโตนิโอฉันมีเซลล์ที่กำหนดเองและชอบที่จะทำสิ่งนี้ในแบบของคุณ อย่างไรก็ตามมันไม่ทำงาน ฉันต้องการให้โค้ด 'รูดเพื่อเปิดเผยปุ่มลบ' ของฉันทำงานซึ่งเป็นเมธอด tableView COMMEditingStyle ฉันลบรหัสนั้นออกจากคลาส mainVC และใส่ไว้ในคลาส customCell แต่ตอนนี้โค้ดใช้ไม่ได้แล้ว ฉันขาดอะไรไป?
Dave G

ฉันคิดว่านี่เป็นวิธีที่ดีที่สุดในการรับ indexPath ของเซลล์ที่มีส่วน x แต่ฉันไม่เห็นความจำเป็นในการใช้สัญลักษณ์แสดงหัวข้อย่อย 3 และ 4 ในแนวทาง MVC
Edward

2

หลังจากเห็นข้อเสนอแนะของ Paulw11 ในการใช้การโทรกลับของผู้ร่วมประชุมฉันต้องการอธิบายรายละเอียดเล็กน้อย / หยิบยกข้อเสนอแนะอื่นที่คล้ายกัน หากคุณไม่ต้องการใช้รูปแบบผู้รับมอบสิทธิ์คุณสามารถใช้การปิดได้อย่างรวดเร็วดังนี้:

ระดับเซลล์ของคุณ:

class Cell: UITableViewCell {
    @IBOutlet var button: UIButton!

    var buttonAction: ((sender: AnyObject) -> Void)?

    @IBAction func buttonPressed(sender: AnyObject) {
        self.buttonAction?(sender)
    }
}

cellForRowAtIndexPathวิธีการของคุณ:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
    cell.buttonAction = { (sender) in
        // Do whatever you want from your button here.
    }
    // OR
    cell.buttonAction = buttonPressed // <- Method on the view controller to handle button presses.
}

2

ฉันพบวิธีที่ง่ายมากในการใช้จัดการเซลล์ใด ๆ ใน tableView และ collectionView โดยใช้คลาส Model และนี่ก็ทำงานได้อย่างสมบูรณ์แบบ

ตอนนี้มีวิธีจัดการที่ดีกว่ามาก สิ่งนี้จะใช้ได้กับการจัดการเซลล์และค่า

นี่คือผลลัพธ์ของฉัน (ภาพหน้าจอ) ดังนั้นดูสิ่งนี้:

ป้อนคำอธิบายภาพที่นี่

  1. มันง่ายมากในการสร้างคลาสโมเดลโปรดทำตามขั้นตอนด้านล่าง สร้างคลาสที่รวดเร็วพร้อมชื่อRNCheckedModelเขียนโค้ดตามด้านล่าง
class RNCheckedModel: NSObject {

    var is_check = false
    var user_name = ""

    }
  1. สร้างชั้นเซลล์ของคุณ
class InviteCell: UITableViewCell {

    @IBOutlet var imgProfileImage: UIImageView!
    @IBOutlet var btnCheck: UIButton!
    @IBOutlet var lblName: UILabel!
    @IBOutlet var lblEmail: UILabel!
    }
  1. และในที่สุดก็ใช้คลาสในรูปแบบของคุณUIViewControllerเมื่อคุณใช้ของคุณUITableView
    class RNInviteVC: UIViewController, UITableViewDelegate, UITableViewDataSource {


    @IBOutlet var inviteTableView: UITableView!
    @IBOutlet var btnInvite: UIButton!

    var checkArray : NSMutableArray = NSMutableArray()
    var userName : NSMutableArray = NSMutableArray()

    override func viewDidLoad() {
        super.viewDidLoad()
        btnInvite.layer.borderWidth = 1.5
        btnInvite.layer.cornerRadius = btnInvite.frame.height / 2
        btnInvite.layer.borderColor =  hexColor(hex: "#512DA8").cgColor

        var userName1 =["Olivia","Amelia","Emily","Isla","Ava","Lily","Sophia","Ella","Jessica","Mia","Grace","Evie","Sophie","Poppy","Isabella","Charlotte","Freya","Ruby","Daisy","Alice"]


        self.userName.removeAllObjects()
        for items in userName1 {
           print(items)


            let model = RNCheckedModel()
            model.user_name = items
            model.is_check = false
            self.userName.add(model)
        }
      }
     @IBAction func btnInviteClick(_ sender: Any) {

    }
       func tableView(_ tableView: UITableView, numberOfRowsInSection 
       section: Int) -> Int {
        return userName.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: InviteCell = inviteTableView.dequeueReusableCell(withIdentifier: "InviteCell", for: indexPath) as! InviteCell

        let image = UIImage(named: "ic_unchecked")
        cell.imgProfileImage.layer.borderWidth = 1.0
        cell.imgProfileImage.layer.masksToBounds = false
        cell.imgProfileImage.layer.borderColor = UIColor.white.cgColor
        cell.imgProfileImage.layer.cornerRadius =  cell.imgProfileImage.frame.size.width / 2
        cell.imgProfileImage.clipsToBounds = true

        let model = self.userName[indexPath.row] as! RNCheckedModel
        cell.lblName.text = model.user_name

        if (model.is_check) {
            cell.btnCheck.setImage(UIImage(named: "ic_checked"), for: UIControlState.normal)
        }
        else {
            cell.btnCheck.setImage(UIImage(named: "ic_unchecked"), for: UIControlState.normal)
        }

        cell.btnCheck.tag = indexPath.row
        cell.btnCheck.addTarget(self, action: #selector(self.btnCheck(_:)), for: .touchUpInside)

        cell.btnCheck.isUserInteractionEnabled = true

    return cell

    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80

    }

    @objc func btnCheck(_ sender: UIButton) {

        let tag = sender.tag
        let indexPath = IndexPath(row: tag, section: 0)
        let cell: InviteCell = inviteTableView.dequeueReusableCell(withIdentifier: "InviteCell", for: indexPath) as! InviteCell

        let model = self.userName[indexPath.row] as! RNCheckedModel

        if (model.is_check) {

            model.is_check = false
            cell.btnCheck.setImage(UIImage(named: "ic_unchecked"), for: UIControlState.normal)

            checkArray.remove(model.user_name)
            if checkArray.count > 0 {
                btnInvite.setTitle("Invite (\(checkArray.count))", for: .normal)
                print(checkArray.count)
                UIView.performWithoutAnimation {
                    self.view.layoutIfNeeded()
                }
            } else {
                btnInvite.setTitle("Invite", for: .normal)
                UIView.performWithoutAnimation {
                    self.view.layoutIfNeeded()
                }
            }

        }else {

            model.is_check = true
            cell.btnCheck.setImage(UIImage(named: "ic_checked"), for: UIControlState.normal)

            checkArray.add(model.user_name)
            if checkArray.count > 0 {
                btnInvite.setTitle("Invite (\(checkArray.count))", for: .normal)
                UIView.performWithoutAnimation {
                self.view.layoutIfNeeded()
                }
            } else {
                 btnInvite.setTitle("Invite", for: .normal)
            }
        }

        self.inviteTableView.reloadData()
    }

    func hexColor(hex:String) -> UIColor {
        var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()

        if (cString.hasPrefix("#")) {
            cString.remove(at: cString.startIndex)
        }

        if ((cString.count) != 6) {
            return UIColor.gray
        }

        var rgbValue:UInt32 = 0
        Scanner(string: cString).scanHexInt32(&rgbValue)

        return UIColor(
            red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
            blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
            alpha: CGFloat(1.0)
        )
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()

    }

     }

1

ฉันใช้วิธีการ convertPoint เพื่อรับจุดจาก tableview และส่งจุดนี้ไปยังเมธอด indexPathForRowAtPoint เพื่อรับ indexPath

 @IBAction func newsButtonAction(sender: UIButton) {
        let buttonPosition = sender.convertPoint(CGPointZero, toView: self.newsTableView)
        let indexPath = self.newsTableView.indexPathForRowAtPoint(buttonPosition)
        if indexPath != nil {
            if indexPath?.row == 1{
                self.performSegueWithIdentifier("alertViewController", sender: self);
            }   
        }
    }

1

ลองใช้ #selector เพื่อเรียก IBaction ใน cellforrowatindexpath

            cell.editButton.tag = indexPath.row
        cell.editButton.addTarget(self, action: #selector(editButtonPressed), for: .touchUpInside)

วิธีนี้คุณสามารถเข้าถึง indexpath ภายในเมธอด editButtonPressed

func editButtonPressed(_ sender: UIButton) {

print(sender.tag)//this value will be same as indexpath.row

}

คำตอบที่เหมาะสมที่สุด
Amalendu Kar

ไม่แท็กจะปิดเมื่อผู้ใช้เพิ่มหรือลบเซลล์
koen

@koen: ไม่ใช่ถ้าคุณโหลด tableView ซ้ำหลังจากแทรกหรือลบ ;-)
mramosch

1

ในกรณีของฉันฉันมีหลายส่วนและทั้งดัชนีส่วนและแถวมีความสำคัญดังนั้นในกรณีนี้ฉันเพิ่งสร้างคุณสมบัติบน UIButton ซึ่งฉันตั้งค่าดัชนีพา ธ ของเซลล์ดังนี้:

fileprivate struct AssociatedKeys {
    static var index = 0
}

extension UIButton {

    var indexPath: IndexPath? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.index) as? IndexPath
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.index, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

จากนั้นตั้งค่าคุณสมบัติใน cellForRowAt ดังนี้:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
    cell.button.indexPath = indexPath
}

จากนั้นใน handleTapAction คุณจะได้รับ indexPath ดังนี้:

@objc func handleTapAction(_ sender: UIButton) {
    self.selectedIndex = sender.indexPath

}

1

Swift 4 และ 5

วิธีที่ 1 โดยใช้ Protocol delegate

ตัวอย่างเช่นคุณมีUITableViewCellชื่อMyCell

class MyCell: UITableViewCell {
    
    var delegate:MyCellDelegate!
    
    @IBAction private func myAction(_ sender: UIButton){
        delegate.didPressButton(cell: self)
    }
}

ตอนนี้สร้างไฟล์ protocol

protocol MyCellDelegate {
    func didPressButton(cell: UITableViewCell)
}

ขั้นตอนต่อไปสร้างส่วนขยายของ UITableView

extension UITableView {
    func returnIndexPath(cell: UITableViewCell) -> IndexPath?{
        guard let indexPath = self.indexPath(for: cell) else {
            return nil
        }
        return indexPath
    }
}

ในUIViewControllerการใช้โปรโตคอลของคุณMyCellDelegate

class ViewController: UIViewController, MyCellDelegate {
     
    func didPressButton(cell: UITableViewCell) {
        if let indexpath = self.myTableView.returnIndexPath(cell: cell) {
              print(indexpath)
        }
    }
}

วิธีที่ 2 โดยใช้การปิด

ใน UIViewController

override func viewDidLoad() {
        super.viewDidLoad()
       //using the same `UITableView extension` get the IndexPath here
        didPressButton = { cell in
            if let indexpath = self.myTableView.returnIndexPath(cell: cell) {
                  print(indexpath)
            }
        }
    }
 var didPressButton: ((UITableViewCell) -> Void)

class MyCell: UITableViewCell {

    @IBAction private func myAction(_ sender: UIButton){
        didPressButton(self)
    }
}

หมายเหตุ: - หากคุณต้องการรับUICollectionViewindexPath คุณสามารถใช้สิ่งนี้UICollectionView extensionและทำซ้ำขั้นตอนข้างต้น

extension UICollectionView {
    func returnIndexPath(cell: UICollectionViewCell) -> IndexPath?{
        guard let indexPath = self.indexPath(for: cell) else {
            return nil
        }
        return indexPath
    }
}

0

ใน Swift 3 ยังใช้คำสั่งยามหลีกเลี่ยงโซ่ยาว

func buttonTapped(sender: UIButton) {
    guard let cellInAction = sender.superview as? UITableViewCell else { return }
    guard let indexPath = tableView?.indexPath(for: cellInAction) else { return }

    print(indexPath)
}

สิ่งนี้จะไม่ทำงาน ซูเปอร์วิวของปุ่มจะไม่ใช่เซลล์
rmaddy

สิ่งนี้ได้ผล สิ่งเดียวที่คุณควรระมัดระวังคือมุมมองของทุกคนแตกต่างกัน อาจเป็น sender.superview, sender.superview.superview หรือ sender.superview.superview.superview แต่มันทำงานได้ดีจริงๆ
ฌอน

0

บางครั้งปุ่มอาจอยู่ในมุมมองอื่นของ UITableViewCell ในกรณีนั้น superview.superview อาจไม่ให้วัตถุเซลล์และด้วยเหตุนี้ indexPath จะเป็นศูนย์

ในกรณีนี้เราควรค้นหา superview ต่อไปจนกว่าเราจะได้วัตถุเซลล์

ฟังก์ชั่นรับวัตถุเซลล์โดย superview

func getCellForView(view:UIView) -> UITableViewCell?
{
    var superView = view.superview

    while superView != nil
    {
        if superView is UITableViewCell
        {
            return superView as? UITableViewCell
        }
        else
        {
            superView = superView?.superview
        }
    }

    return nil
}

ตอนนี้เราสามารถรับ indexPath บนปุ่มแตะด้านล่าง

@IBAction func tapButton(_ sender: UIButton)
{
    let cell = getCellForView(view: sender)
    let indexPath = myTabelView.indexPath(for: cell)
}

0
// CustomCell.swift

protocol CustomCellDelegate {
    func tapDeleteButton(at cell: CustomCell)
}

class CustomCell: UICollectionViewCell {
    
    var delegate: CustomCellDelegate?
    
    fileprivate let deleteButton: UIButton = {
        let button = UIButton(frame: .zero)
        button.setImage(UIImage(named: "delete"), for: .normal)
        button.addTarget(self, action: #selector(deleteButtonTapped(_:)), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    @objc fileprivate func deleteButtonTapped(_sender: UIButton) {
        delegate?.tapDeleteButton(at: self)
    }
    
}

//  ViewController.swift

extension ViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier, for: indexPath) as? CustomCell else {
            fatalError("Unexpected cell instead of CustomCell")
        }
        cell.delegate = self
        return cell
    }

}

extension ViewController: CustomCellDelegate {

    func tapDeleteButton(at cell: CustomCell) {
        // Here we get the indexPath of the cell what we tapped on.
        let indexPath = collectionView.indexPath(for: cell)
    }

}

0

การใช้แท็กเดียวสำหรับแถวและส่วนต่างๆ

มีวิธีง่ายๆในการใช้แท็กสำหรับการส่งแถว / รายการและส่วนของ TableView / CollectionView ในเวลาเดียวกัน

เข้ารหัส IndexPathสำหรับ UIView.tag คุณใน cellForRowAtIndexPath :

buttonForCell.tag = convertIndexPathToTag(with: indexPath)

ถอดรหัส IndexPathจากผู้ส่งของคุณในการเลือกเป้าหมายของคุณ:

    @IBAction func touchUpInsideButton(sender: UIButton, forEvent event: UIEvent) {

        var indexPathForButton = convertTagToIndexPath(from: sender)

    }

ตัวเข้ารหัสและตัวถอดรหัส:

func convertIndexPathToTag(indexPath: IndexPath) -> Int {
    var tag: Int = indexPath.row + (1_000_000 * indexPath.section)
    
    return tag
}

func convertTagToIndexPath(from sender: UIButton) -> IndexPath {
    var section: Int = Int((Float(sender.tag) / 1_000_000).rounded(.down))
    var row: Int = sender.tag - (1_000_000 * section)

    return IndexPath(row: row, section: section)
}

โดยมีเงื่อนไขว่าคุณไม่ต้องการมากกว่า 4294967296 แถว / รายการบนอุปกรณ์ 32 บิต ;-) เช่น

  • 42949 ส่วนที่มี 100_000 รายการ / แถว
  • 4294 ส่วนที่มี 1_000_000 รายการ / แถว - ( เช่นในตัวอย่างด้านบน )
  • 429 ส่วนที่มี 10_000_000 รายการ / แถว

—-

คำเตือน: โปรดทราบว่าเมื่อลบหรือแทรกแถว / รายการใน TableView / CollectionView คุณต้องโหลดแถว / รายการทั้งหมดใหม่หลังจากจุดแทรก / ลบเพื่อให้หมายเลขแท็กของปุ่มตรงกับโมเดลของคุณ

—-

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.