UITableView reloadData自动调用resignFirstResponder

发布于 2024-11-16 11:54:44 字数 447 浏览 5 评论 0原文

我有这个带有自定义单元格的 UITableView,只能获取预定义的值,因此我使用 UIPickerView 作为其 inputView。一切都非常好,直到我编辑字段并需要显示其更新值。

为了使事情更清晰、更易于维护,我将委托和数据源作为单独的类,并使用通知使它们与 tableView 交互。因此,从 UIPickerView 选择一个值后,tableView 的数据源会收到通知,并依次通知持有 tableView 引用的主 ViewController。从那里我打电话

[_tableView reloadData];

,一切似乎都正常,除了 UIPickerView 消失了,我想是因为单元格被重新生成,并且在某个地方调用了一些 resignFirstResponder ,或者类似的东西。 有没有其他方法可以使 tableView 更新其值,而不必在某个地方实现自定义方法,这会很丑陋?

I have this UITableView with custom cells that can get only predefined values, therefore I use a UIPickerView as their inputView. All is jolly good until I edit a field and need to show its updated value.

In order to make things clearer and easier to maintain, I made delegates and data sources as separate classes, and use notifications to make them interact with the tableView. So, after a value has been chosen from the UIPickerView, the tableView's data source gets notified, and in turn notifies the main ViewController that holds a reference to the tableView. From there I call

[_tableView reloadData];

and everything seems to work, except that the UIPickerView disappears, I think because the cells are regenerated and somewhere some resignFirstResponder is called, or something like that.
Is there any other way to make the tableView updating its values without having to implement a custom method somewhere that does it, which would be quite ugly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

白日梦 2024-11-23 11:54:44

这读起来就像预期的行为 - 选择器属于特定的单元格,该单元格被重新加载并且不再是第一响应者。我猜想无论如何,人们都必须选择一个特定的元素才能显示选择器,即使其成为第一响应者。

因此,您要么需要在重新加载后使其再次成为第一响应者,要么直接更新特定的单元格。

This reads like expected behavior - the picker belongs to a particular cell, that cell gets reloaded and is not the first responder any more. I guess one had to select a specific element anyway for the picker to appear, i.e. to make it first responder.

So you either need to make it become first responder again after reloading, or update the specific cell directly.

彻夜缠绵 2024-11-23 11:54:44

添加:

[yourSearchBar becomeFirstResponder];

在你的之后:

[_tableView reloadData];

成功了

adding:

[yourSearchBar becomeFirstResponder];

after your:

[_tableView reloadData];

did the trick

孤凫 2024-11-23 11:54:44

我遇到了同样的问题,上面的答案都不能完美工作(我看到键盘上下弹跳等)。
解决了这个问题

[tableView beginUpdates];
[tableView endUpdates]; 

this SO post之后,我通过调用这对我有用 ,表格行得到更新,甚至扩展/收缩(如果您动态更改行高)并带有漂亮的动画,所有这些都无需放弃第一响应者,甚至无需启动键盘关闭。
这不会滚动您的表格视图以适合任何展开的行,因此我将上面的代码片段放在专用方法 fe 中:

- (void)tableView:(UITableView *)tableView reloadRowWhileShowingKeyboard:(NSIndexPath *)indexPath 
{  
    [tableView beginUpdates];
    [tableView endUpdates]; 

    [tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

I met the same problem, none of the answers above worked perfectly (I see the keyboard bouncing up and down, etc.).
Following this SO post I fixed the issue by calling

[tableView beginUpdates];
[tableView endUpdates]; 

this worked for me, table rows get updates and even expand/shrink (if you are changing rows height dynamically) with a nice animation, all without resigning first responder or even starting keyboard dismiss.
This will not scroll your table view to fit any expanded row, so I put the snippet above in dedicated method, f.e.:

- (void)tableView:(UITableView *)tableView reloadRowWhileShowingKeyboard:(NSIndexPath *)indexPath 
{  
    [tableView beginUpdates];
    [tableView endUpdates]; 

    [tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
知你几分 2024-11-23 11:54:44

您可以遵循这种方法,虽然不是最好的,但它有效:

// pass the responder to a temporary (hidden) textField
[_tmpTextField becomeFirstResponder];

// reload data
[_tableView reloadData];

// reloadData is definitely async... 
// so pass the responder back in a timed op
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [_textField becomeFirstResponder];
});

You can follow this approach, not the best, but it works:

// pass the responder to a temporary (hidden) textField
[_tmpTextField becomeFirstResponder];

// reload data
[_tableView reloadData];

// reloadData is definitely async... 
// so pass the responder back in a timed op
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [_textField becomeFirstResponder];
});
寄居者 2024-11-23 11:54:44

我通过子类化 UITextView、覆盖 -(BOOL)resignFirstResponder 并添加 BOOL canResign 解决了这个问题。该变量在重新加载数据之前设置,并在不久后取消设置。

I solved this by subclassing UITextView, overriding -(BOOL)resignFirstResponder and by adding a BOOL canResign. this variable is set before reloading the data and unset a short time after.

ゞ记忆︶ㄣ 2024-11-23 11:54:44
customTextField.canResign = NO;
[self.tableView reloadData];
customTextField.canResign = YES;

自定义文本字段源自 UITextField。

.h

@interface CustomTextField : UITextField
@property (nonatomic,assign) BOOL canResign;
@end

.m

- (BOOL)canResignFirstResponder
{
    return self.canResign;
}

确保您的自定义文本字段在表格视图重新加载时不会重新创建

customTextField.canResign = NO;
[self.tableView reloadData];
customTextField.canResign = YES;

Custom text field is derived from UITextField.

.h

@interface CustomTextField : UITextField
@property (nonatomic,assign) BOOL canResign;
@end

.m

- (BOOL)canResignFirstResponder
{
    return self.canResign;
}

Make sure that your custom text field is not recreated on table view reloading.

甜`诱少女 2024-11-23 11:54:44

我将 UISearchBar 放在 UITableView 中自己的部分中。当启动搜索时,我确保只刷新不包含搜索栏的部分。

- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation

I put my UISearchBar in its own section in a UITableView. When firing off the search, I made sure to only refresh the sections which do not contain the search bar.

- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
甜妞爱困 2024-11-23 11:54:44

Swift 解决方案:

我们可以通过子类化 UITextfiled 来覆盖默认的 canResignFirstResponder ,

class CustomField: UITextField{
    var canResign:Bool = false
    override var canResignFirstResponder: Bool{
        return canResign
    }  
}

您需要在重新加载语句之前和之后设置 canResign 变量。

cell.offerInputTextField.canResign = false
tableView.reloadData()
cell.offerInputTextField.canResign = true

不要忘记将自定义类文本字段指定为 CustomField。

Swift solution:

we can override default canResignFirstResponder by subclassing UITextfiled

class CustomField: UITextField{
    var canResign:Bool = false
    override var canResignFirstResponder: Bool{
        return canResign
    }  
}

all you need to set canResign variable before and after reload statement.

cell.offerInputTextField.canResign = false
tableView.reloadData()
cell.offerInputTextField.canResign = true

don't forget to assign the custom class text field as CustomField.

扶醉桌前 2024-11-23 11:54:44

这是我的解决方案。认为搜索文本字段位于索引 0 中。您需要更新或添加或删除下一行。

let beforeCount = self.filteredItems.count
self.filteredItem = ... next filtered items

var removalIndexPaths = [IndexPath]()
var appendalIndexPaths = [IndexPath](
var modifyIndexPaths = [IndexPath]()
self.tableView.beginUpdates()
let deleteAvailable = beforeCount != 0
if deleteAvailable {
    for i in stride(from: beforeCount - 1, to: 0, by: -1) {
        removalIndexPaths.append(IndexPath(row: i, section: 0))
     }
}
        
let appendAvailable = self.filteredShots.count != 0
if appendAvailable {
     for i in stride(from: 1, to: self.filteredShots.count, by: 1) {
         appendalIndexPaths.append(IndexPath(row: i, section: 0))
      }
 }
        
 modifyIndexPaths = appendalIndexPaths.filter { appendPath in
      if appendPath == removalIndexPaths.filter({ $0 == appendPath }).first {
         return true
     } else {
         return false
     }
 }
        
for item in modifyIndexPaths {
     if let rai = appendalIndexPaths.firstIndex(where: { $0 == item}) {
          appendalIndexPaths.remove(at: rai)
     }
     if let rri = removalIndexPaths.firstIndex(where: { $0 == item}) {
           removalIndexPaths.remove(at: rri)
      }
}
        
if modifyIndexPaths.count > 0 {
      self.tableView.reloadRows(at: modifyIndexPaths, with: .none)
}
        
if removalIndexPaths.count > 0 {
      self.tableView.deleteRows(at: removalIndexPaths, with: .none)
}
        
if appendalIndexPaths.count > 0 {
       self.tableView.insertRows(at: appendalIndexPaths, with: .none)
}
               
self.tableView.endUpdates()

Here my solution. Think that search textfield is in index 0.. You need to update or add or remove next rows..

let beforeCount = self.filteredItems.count
self.filteredItem = ... next filtered items

var removalIndexPaths = [IndexPath]()
var appendalIndexPaths = [IndexPath](
var modifyIndexPaths = [IndexPath]()
self.tableView.beginUpdates()
let deleteAvailable = beforeCount != 0
if deleteAvailable {
    for i in stride(from: beforeCount - 1, to: 0, by: -1) {
        removalIndexPaths.append(IndexPath(row: i, section: 0))
     }
}
        
let appendAvailable = self.filteredShots.count != 0
if appendAvailable {
     for i in stride(from: 1, to: self.filteredShots.count, by: 1) {
         appendalIndexPaths.append(IndexPath(row: i, section: 0))
      }
 }
        
 modifyIndexPaths = appendalIndexPaths.filter { appendPath in
      if appendPath == removalIndexPaths.filter({ $0 == appendPath }).first {
         return true
     } else {
         return false
     }
 }
        
for item in modifyIndexPaths {
     if let rai = appendalIndexPaths.firstIndex(where: { $0 == item}) {
          appendalIndexPaths.remove(at: rai)
     }
     if let rri = removalIndexPaths.firstIndex(where: { $0 == item}) {
           removalIndexPaths.remove(at: rri)
      }
}
        
if modifyIndexPaths.count > 0 {
      self.tableView.reloadRows(at: modifyIndexPaths, with: .none)
}
        
if removalIndexPaths.count > 0 {
      self.tableView.deleteRows(at: removalIndexPaths, with: .none)
}
        
if appendalIndexPaths.count > 0 {
       self.tableView.insertRows(at: appendalIndexPaths, with: .none)
}
               
self.tableView.endUpdates()
会傲 2024-11-23 11:54:44

如果您在搜索栏时遇到此问题,以下内容在 iOS 6 中为我解决了:

  • 实例化 UISearchBar 并将其作为子视图添加到顶部的 UITableView。
  • 在 UITableView 中创建一个虚拟的第一个单元格,以便搜索栏仅阻止该虚拟单元格,而不阻止包含数据的实际单元格。

If you are facing this issue with a search bar, the following did it for me in iOS 6:

  • Instantiate a UISearchBar and add it as a subview to your UITableView at the top.
  • Create a dummy first cell in your UITableView so that the search bar only blocks this dummy cell and not your actual cell with data.
写下不归期 2024-11-23 11:54:44

正如@Eiko 提到的,这对我有用!

更新 UIPickerViewDelegate 的 pickerView:didSelectRow:inComponent: 方法中的单元格:

- (void) pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
  TableViewCell *cell = (TableViewCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:pickerView.tag inSection:0]];

  /*
  Update your cell here. 
  */

  // Reload TableViewCell will resign the PickerView, so we need to focus it back.  
  [self.tableView reloadData];
  NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
  NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
  [self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationNone];
  [cell.textField becomeFirstResponder];
}

As mentioned by @Eiko, this works for me!

Update the cell in UIPickerViewDelegate's pickerView:didSelectRow:inComponent: method:

- (void) pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
  TableViewCell *cell = (TableViewCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:pickerView.tag inSection:0]];

  /*
  Update your cell here. 
  */

  // Reload TableViewCell will resign the PickerView, so we need to focus it back.  
  [self.tableView reloadData];
  NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
  NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
  [self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationNone];
  [cell.textField becomeFirstResponder];
}
苦行僧 2024-11-23 11:54:44

跟踪哪个单元格的键盘处于活动状态,然后通过 cellForRowAtIndexPath 获取该特定单元格并使 textView firstResponder

self.tableView.reloadData()
if let indexPath = self.activeIndexPath{
   if let cell = createFormTableView.cellForRow(at: indexPath) as? TextViewTableViewCell {
        cell.txtViewInput.becomeFirstResponder()
   }
}

Track which cell's keyboard is active and then get that particular cell by cellForRowAtIndexPath and make textView firstResponder

self.tableView.reloadData()
if let indexPath = self.activeIndexPath{
   if let cell = createFormTableView.cellForRow(at: indexPath) as? TextViewTableViewCell {
        cell.txtViewInput.becomeFirstResponder()
   }
}
半边脸i 2024-11-23 11:54:44

我使用 beginUpdateendUpdate
结束更新后,获取包含已具有焦点的文本字段的单元格,然后使其成为第一响应者

    self.tableView.beginUpdates()
    self.tableView.reloadRows(at: [indexPath], with: .automatic)
    self.tableView.endUpdates()
    let newCell = self.tableView.cellForRow(at: indexPath)
    newCell.textField.becomeFirstResponder()

I use beginUpdate and endUpdate
After end update, get the cell contains the textfield already has focus then make it first responder

    self.tableView.beginUpdates()
    self.tableView.reloadRows(at: [indexPath], with: .automatic)
    self.tableView.endUpdates()
    let newCell = self.tableView.cellForRow(at: indexPath)
    newCell.textField.becomeFirstResponder()
乖乖兔^ω^ 2024-11-23 11:54:44

所以我测试了很多方法,但看起来只有一种方法仍然看起来不错。

首先,我将回顾一下当前需要避免的 TableView 约束。

  • 当您重新加载 tableview 时,它将在所有单元格上调用 resignFirstResponder
  • 如果您避免在作为 firstResponder 的单元格上调用 reload,这是一个可能的解决方案。
    • 使用 reloadRows(at:with:)reloadSections 等 api。
    • 但这种方法的问题是,如果在调用上述 API 时行数或节数发生变化,则可能会因不一致而崩溃。
    • 非常脆弱的解决方案
  • ,当您删除作为第一响应者的视图时,它会resignFirstResponder

最终解决方案

  • 就在您reloadData()

    之前

  • 通过visibleCells等API获取作为第一响应者的单元格

  • UITextField/UITextView的hidden属性设置为正确

  • textView 移动到通过调用 tableView.addSubview(cell.textField) 查看层次结构(我使用了 tableview),记住不要调用 removeFromSuperview

  • 在某处保留对此 textView 的引用,以便在下一步中使用。

  • tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) 数据源方法中

  • 在相应单元格上设置 textView。

  • 将隐藏设置为 false

So I have tested many ways but it looks like there is only one way that still looks good.

First I'll go over the current constraints you need to avoid of the TableView.

  • when you reload a tableview it will call resignFirstResponder on all cells.
  • if you avoid calling reload on the cell which is the firstResponder it is a possible solution.
    • using such apis as reloadRows(at:with:) or reloadSections.
    • But the issue with this approach is if the number of rows or sections changed when you call the above API it will probably crash due to an inconsistency.
    • very brittle solution
  • when you remove a view which is the first responder it will resignFirstResponder

Final Solution

  • Right before you reloadData()

  • get the cell which is the first responder through API's such as visibleCells

  • set the hidden property of the UITextField/UITextView to true

  • move the textView to a view in the view hierarchy(I used the tableview) by calling tableView.addSubview(cell.textField) remember don't call removeFromSuperview

  • keep a reference to this textView somewhere to be used in the next step.

  • in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) datasource method

  • Set the textView on the corresponding cell.

  • set hidden to false

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文