iOS 开发 Swift 自定义 Cells

Posted

技术标签:

【中文标题】iOS 开发 Swift 自定义 Cells【英文标题】:iOS development Swift Custom Cells 【发布时间】:2017-01-21 19:30:51 【问题描述】:

我正在使用 Swift 开发一个应用程序,但我遇到了自定义单元格的问题。我有一个自定义单元格,当您单击添加按钮时,它会创建另一个自定义单元格。单元格有 3 个文本字段和 2 个按钮。这些文本字段是我正在创建的膳食成分的名称、价格和数量。当我只使用一个自定义单元格或再添加一个以使其成为两个时,数据将正确存储。但是当我添加 2 个自定义单元格(总共 3 个自定义单元格)时,我遇到了价格错误的问题,只计算了最后两种成分的价格。当我添加 3 个自定义单元格(总共 4 个自定义单元格)时,它只会重新创建第一个单元格,其中包含第一个单元格中的填充数据。 在完成按钮点击时,我收到一个致命错误:在展开可选值时发现 nil。

视图控制器

import UIKit
import CoreData

class AddMealViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate 

    @IBOutlet weak var mealNameTF: UITextField!

    @IBOutlet weak var addMealsCell: UITableViewCell!
    @IBOutlet weak var finishButton: UIButton!
    @IBOutlet weak var resetButton: UIButton!
    @IBOutlet weak var addButton: UIButton!
    @IBOutlet weak var addMealTableView: UITableView!

    @IBOutlet weak var productPrice: UILabel!
    let currency = "$" // this should be determined from settings
    var priceTotal = "0"

    override func viewDidLoad() 
        super.viewDidLoad()
        addMealTableView.delegate = self
        addMealTableView.dataSource = self
        borderToTextfield(textField: mealNameTF)
        mealNameTF.delegate = self
    

    func textFieldShouldReturn(_ textField: UITextField) -> Bool 
            self.view.endEditing(true)
            return true;
        

    

    func numberOfSections(in tableView: UITableView) -> Int 
        return counter
    

    var counter = 1

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return counter
    

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

        borderToTextfield(textField: (cell.amountTF)!)
        borderToTextfield(textField: (cell.ingredientNameTF)!)
        //borderToTextfield(textField: cell.normativeTF)
        borderToTextfield(textField: (cell.priceTF)!)



        return cell

    


    @IBAction func addButton(_ sender: UIButton) 
        counter += 1

        addMealTableView.register(AddMealsTableViewCell.self, forCellReuseIdentifier: "addMealsCell")
        addMealTableView.reloadData()

    



    @IBAction func resetButton(_ sender: UIButton) 
       mealNameTF.text = ""
        for c in 0..<counter
            let indexPath = IndexPath(row: c, section:0)
            let cell = addMealTableView.cellForRow(at: indexPath) as! AddMealsTableViewCell
            cell.amountTF.text = ""
            cell.ingredientNameTF.text = ""
          //  cell.normativeTF.text = ""
            cell.priceTF.text = ""
        
        productPrice.text = "\(currency)0.00"
        priceTotal = "0"
        counter = 1
    

    @IBAction func finishButton(_ sender: UIButton) 
        for c in (0..<counter)
            if let cell = addMealTableView.cellForRow(at: IndexPath(row: c, section: 0)) as? AddMealsTableViewCell 
                cell.amountTF.delegate = self
                cell.ingredientNameTF.delegate = self
                // cell.normativeTF.delegate = self
                cell.priceTF.delegate = self

                guard cell.priceTF.text?.isEmpty == false && cell.amountTF.text?.isEmpty == false && mealNameTF.text?.isEmpty == false && cell.ingredientNameTF.text?.isEmpty == false
                    else 
                        return
                

                if cell.priceTF.text?.isEmpty == false
                //    if (true) 
                    let tfp = Double((cell.priceTF.text!))!*Double((cell.amountTF.text!))!
                    var ttp = Double(priceTotal)
                    ttp! += tfp
                    priceTotal = String(ttp!)
              //  
                    
            


        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let context = appDelegate.persistentContainer.viewContext
        let newMeal = NSEntityDescription.insertNewObject(forEntityName: "Meal", into: context)
        let mealName = mealNameTF.text
        newMeal.setValue(mealName, forKey: "name")
        newMeal.setValue(priceTotal, forKey: "price")
        do 
            try context.save()

            print("Spremljeno")
         catch  
            print("Neki error")
        

        productPrice.text = currency + priceTotal
    

    @IBAction func addNewIngredientButton(_ sender: UIButton) 


    
    func borderToTextfield(textField: UITextField)
        let border = CALayer()
        let width = CGFloat(2.0)
        border.borderColor = UIColor.white.cgColor
        border.frame = CGRect(x: 0, y: textField.frame.size.height - width, width:  textField.frame.size.width, height: textField.frame.size.height)

        border.borderWidth = width
        textField.layer.addSublayer(border)
        textField.layer.masksToBounds = true
        textField.tintColor = UIColor.white
        textField.textColor = UIColor.white
        textField.textAlignment = .center

    

    func textFieldShouldReturn(_ textField: UITextField) -> Bool 
        self.view.endEditing(true)
        return true;
    

单元格

class AddMealsTableViewCell: UITableViewCell, UITextFieldDelegate

    @IBOutlet weak var DropMenuButton: DropMenuButton!
    @IBOutlet weak var addNewIngredient: UIButton!
    @IBOutlet weak var ingredientNameTF: UITextField!

   // @IBOutlet weak var normativeTF: UITextField!

    @IBOutlet weak var amountTF: UITextField!

    @IBOutlet weak var priceTF: UITextField!

    func textFieldShouldReturn(_ textField: UITextField) -> Bool 
        self.endEditing(true)
        return true;
    

    override func prepareForReuse() 
        self.amountTF.text = ""
        self.priceTF.text = ""
        self.ingredientNameTF.text = ""
    

    @IBAction func DropMenuButton(_ sender: DropMenuButton) 
        DropMenuButton.initMenu(["kg", "litre", "1/pcs"], actions: [( () -> (Void) in
            print("kg")
            sender.titleLabel?.text = "kg"
        ), ( () -> (Void) in
            print("litre")

            sender.titleLabel?.text = "litre"
        ), ( () -> (Void) in
            print("1/pcs")
            sender.titleLabel?.text = "1/pcs"
        )])
    

    @IBAction func addNewIngredient(_ sender: UIButton) 


        let name = ingredientNameTF.text
        let amount = amountTF.text
        let price = priceTF.text
        //  let normative = normativeTF.text

        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let context = appDelegate.persistentContainer.viewContext
        let newIngredient = NSEntityDescription.insertNewObject(forEntityName: "Ingredient", into: context)

       newIngredient.setValue(name, forKey: "name")
       // newIngredient.setValue(normative, forKey: "normative")
       newIngredient.setValue(amount, forKey: "amount")
       newIngredient.setValue(price, forKey: "price")

        do 

            try context.save()
            print("Spremljeno")
         catch  
            print("Neki error")
        
    

【问题讨论】:

【参考方案1】:

您的代码很难阅读,但我怀疑问题可能出在这里:

func numberOfSections(in tableView: UITableView) -> Int 
    return counter


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    return counter

您返回的部分行数和部分行数相同。因此,如果您有 1 种成分,则表示有 1 个部分和 1 行。但是,如果您有 2 种成分,则表示有 2 个部分,每个部分有 2 个单元格(总共 4 个单元格)。

您的代码还有许多其他问题需要修复,这里有一些:

最重要的是,您使用 counter 变量使这变得非常困难。相反,如果您有一系列成分

var ingredients = [Ingredient]()

您可以使用它来设置您的桌子计数的所有内容。像这样的:

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


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

    let ingredient = ingredients[indexPath.row]

    cell.ingredientNameTF.text = ingredient.name
    cell.normativeTF.text = ingredient.normative
    cell.amountTF.text = ingredient.amount
    cell.priceTF.text = ingredient.price

    return cell

所有这些都可以在界面生成器中设置(您不需要在视图中为它们加载的代码行):

    addMealTableView.delegate = self
    addMealTableView.dataSource = self
    mealNameTF.delegate = self

这一行应该在你的viewDidLoad函数中,你只需要注册一次类,你每次按add时都在做。

addMealTableView.register(AddMealsTableViewCell.self, forCellReuseIdentifier: "addMealsCell")

你的动作名称应该是动作

 @IBAction func addButtonPressed(_ sender: UIButton)
 @IBAction func resetButtonPressed(_ sender: UIButton)
 @IBAction func finishButtonPressed(_ sender: UIButton)

谢谢。现在,当 ViewController 加载时,我没有单元格。我的数组现在是空的,所以我必须实现一些代码来创建另一个单元格并重新加载 tableView。我该怎么做?

您只需要在成分数组中添加一个新的成分对象并重新加载表格视图的数据即可。

@IBAction func addButtonPressed(_ sender: UIButton) 
    let newIngredient = Ingredient()
    ingredients.append(newIngredient)
    tableView.reloadData()

【讨论】:

好的,谢谢。但是当我这样做时,会出现另一个问题。当我加载该自定义单元格所在的表格视图时,它会失败,并显示:索引超出范围。我该怎么做才能解决这个问题? @Alen 哪一行因该错误而失败? @CodeLogic 在这一行:让成分 = 成分 [indexPath.row] 在我的控制台输出中我得到:致命错误:索引超出范围 那么您没有上面的numberOfRowsInSection 代码,或者您正在添加一行而没有将ingredient 添加到ingredients 数组中 谢谢。现在,当 ViewController 加载时,我没有单元格。我的数组现在是空的,所以我必须实现一些代码来创建另一个单元格并重新加载 tableView。我该怎么做?

以上是关于iOS 开发 Swift 自定义 Cells的主要内容,如果未能解决你的问题,请参考以下文章

自定义 iOS8 标注气泡 (Swift)

iOS 上 Swift 中的自定义 SceneKit 几何图形不起作用,但等效的 Objective C 代码可以

Swift 中的自定义地图

如何在iOS swift中实现带有月份、日期和时间的自定义选择器?

swift中的自定义集合视图单元格

在 Swift 中归档自定义对象 - 扩展