前提・実現したいこと
Todoアプリを制作しているのですが、UIButtonを使ったチェックボックスの挙動がおかしいので質問させていただきました。
アプリはRealmに保存してあるデータを読み込んで表示させているのですが、
初期表示時に「未チェック(false)」となっているチェックボックスを1回クリックしても、「チェック済(true)」状態になりません。
もう一度クリックすると、やっと「チェック済(true)」になります。
発生している問題・エラーメッセージ
- チェックボックスはカスタムクラス内のdidsetでプロパティ監視をして作成しているのですが、
didset内でデバッグコードを書くと、oldValueの値に変更前の値と逆の物が入っています。
(本来ならtrueの筈なのにfalseが入っている)
- チェックボックスをクリックして最初に入るクラスのsenderのBoolを調べると、本来なら「true」が入るはずなのに「false」になってしまっています。
該当のソースコード
swift
1import UIKit 2 3class CheckBox: UIButton { 4 // Images 5 let checkedImage = UIImage(named: "check_on")! as UIImage 6 let uncheckedImage = UIImage(named: "check_off")! as UIImage 7 8 // Bool property 9 10 var isChecked: Bool = false { 11 12 didSet{ 13 print("old",oldValue,"new",isChecked) 14 if isChecked == true { 15 self.setImage(checkedImage, for: UIControl.State.normal) 16 } else { 17 self.setImage(uncheckedImage, for: UIControl.State.normal) 18 } 19 print("old",oldValue,"new",isChecked) 20 } 21 } 22 23 func nowChecked(check:Bool) { 24 if check == true { 25 self.setImage(checkedImage, for: UIControl.State.normal) 26 } else { 27 self.setImage(uncheckedImage, for: UIControl.State.normal) 28 } 29 } 30 31 override func awakeFromNib() { 32 self.addTarget(self, action:#selector(buttonClicked(sender:)), for: UIControl.Event.touchUpInside) 33 } 34 35 36 @objc func buttonClicked(sender: UIButton) { 37 if sender == self { 38 isChecked.toggle() 39 } 40 } 41} 42
swift
1import UIKit 2 3class TodoCell:UITableViewCell, UITextFieldDelegate{ 4 5 @IBOutlet weak var checkBtn: CheckBox! 6 @IBOutlet weak var todoText: UITextField! 7 @IBOutlet weak var infoBtn: UIButton! 8 9 internal func textFieldShouldReturn(_ textField: UITextField) -> Bool { 10 textField.resignFirstResponder() 11 return true 12 } 13 14 let checkedImage = UIImage(named: "check_on")! as UIImage 15 let uncheckedImage = UIImage(named: "check_off")! as UIImage 16 17 var todoIndex = 0 18 var todoId = "" 19 var tododate = Date() 20 21 22 23 @IBAction func checkView(_ sender: CheckBox) { 24 let check:Bool = sender.isChecked 25 print(check,"に変更") 26 27 InfoHelper().update(id:todoId,title:todoText.text!, date:tododate, check: check) 28 29 } 30} 31
swift
1import UIKit 2import RealmSwift 3 4class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIViewControllerTransitioningDelegate { 5 6 var itemList: Results<TodoItem>! 7 let realm = try! Realm() 8 var token:NotificationToken! 9 var index = 0 10 var sendIndex = 0 11 12 @IBOutlet weak var tableView: UITableView! 13 14 override func viewDidLoad() { 15 16 // realmのリストを取得 17 itemList = realm.objects(TodoItem.self).sorted(byKeyPath: "date") 18 token = realm.observe{ notification, realm in 19 20 //変更があった場合にtableViewを更新 21 self.tableView.reloadData() 22 super.viewDidLoad() 23 } 24 } 25 func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 26 return true 27 } 28 29 func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 30 if editingStyle == .delete { 31 InfoHelper().deleteItem(item: itemList[indexPath.row], token: token) 32 tableView.deleteRows(at: [indexPath], with: .automatic) 33 } 34 } 35 36 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 37 return itemList.count 38 } 39 40 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 41 let cell = tableView.dequeueReusableCell(withIdentifier: "todoCell", for: indexPath) as! TodoCell 42 let item = itemList[indexPath.row] 43 cell.todoId = item.id 44 cell.todoText.text = item.title 45 cell.tododate = item.date 46 cell.checkBtn.nowChecked(check: item.check) 47 cell.infoBtn.tag = indexPath.row 48 cell.todoText.tag = Int(item.id)! 49 50 // テキスト入力が終わったタイミングでメソッド走るように指定 51 cell.todoText.addTarget(self, action: #selector(self.textFieldEditingChanged(_:)), for: .editingDidEndOnExit) 52 53 print(item.title,item.check) 54 55 return cell 56 }
試したこと
デバッグコードを入れて正しい値が入っているか確認しました。
補足情報(FW/ツールのバージョンなど)
Swift 5.4
Xcode 12.5.1
ViewController で nowChecked を呼ぶんじゃなくて isChecked を設定すべきでは。
以前はその表記(cell.checkBtn.isChecked = item.check)にしていたのですが、それだとチェックボックスを押しても反応しなくなってしまう(Booleanの値ごと変わらない)ため、nowCheckedのメソッドを新たに作り、それを呼び出していました。
というか、ボタン押されたら Realm のデータを更新する必要があるのでは…。
言葉足らずですみません、RealmのデータはTodoCell内の「@IBAction func checkView」で更新しています。
チェックボタンを押すと、InfoHelper()というクラスの更新メソッドに値が渡るようにしています。
ボタンを押すと CheckBox クラスの buttonClicked と、TodoCell クラスの checkView の両方が呼ばれるってことですか? その場合、どっちが先に呼ばれるかによって挙動が変わるのでは。buttonClicked が先に呼ばれれば、InfoHelper().update に渡る check の値は新しい値だけど、checkView が先なら古い値になるのでは。
両方が呼ばれています。
確認したところ、先に呼ばれているのはcheckViewの方でした。
buttonClickedの方が先に呼ばれるようにしたいのですが、
IBActionを優先しない書き方がわからずにいます…。
たぶん順番を変えるのは難しい (というか、順番は保証されない) と思うので、CheckBox クラスの buttonClicked を削除して、TodoCell クラスの checkView の先頭で sender.isChecked.toggle() してしまうのが早い気がします。
その方法で出来ました!教えていただきありがとうございます!!
回答1件
あなたの回答
tips
プレビュー