按字母导航 tvOS UITableView

huangapple go评论73阅读模式
英文:

Navigating tvOS UITableView By Letter of Alphabet

问题

这里是关于YouTube上显示问题的剪辑。视频

我有一个UITableView,按字母顺序列出了应用程序中的文件。由于有超过1500个文件,我正在尝试在远程控制器上点击右按钮时实现按字母表导航的功能。我遇到的问题是,TableView确实移动到了正确的位置,但与通常的高亮显示不同,我得到了一个较浅的高亮颜色,并且点击选择不起作用。如果我按下箭头键正常导航,它会将它移回到列表中的第二项。我做错了什么?

- (void)viewDidLoad {
    [super viewDidLoad];

    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    self.definesPresentationContext = YES;  // 确定您希望在哪里显示UISearchController
    self.currentIndex = 0;

    [self populateTheView];
}

- (void)populateTheView {
    NSBundle *bundle = [NSBundle mainBundle];
    self.title = @"Devo Songs";
    self.files  = [bundle pathsForResourcesOfType:@"pdf" inDirectory:@"WorshipSongs"];
    NSString *documentsDirectoryPath = [self.files objectAtIndex:thepath.row];

    self.filenames = [[documentsDirectoryPath lastPathComponent] stringByDeletingPathExtension];

    NSMutableArray *names = [NSMutableArray arrayWithCapacity:[self.files count]];
    for (NSString *path in self.files) {
        [names addObject:[[path lastPathComponent] stringByDeletingPathExtension]];
    }

    //self.files = names;
    self.files = [names sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
    NSLog(@"FILESLOAD%@", self.files);
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    [self becomeFirstResponder]; // 使视图控制器成为第一个响应者以处理远程控制事件。
}

- (BOOL)canBecomeFirstResponder {
    return YES;
}

// 重写pressesBegan方法以检测右箭头键按下事件。
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
    [super pressesEnded:presses withEvent:event];

    for (UIPress *press in presses) {
        if (press.type == UIPressTypeRightArrow) {
            [self handleRightButtonPress];
        }
    }
}

- (void)handleRightButtonPress {
    // 获取当前名称。
    NSString *currentName = self.files[self.currentIndex];

    // 获取当前字母。
    NSString *currentLetter = [currentName substringToIndex:1];

    // 从当前索引开始查找下一个索引。
    NSInteger nextIndex = self.currentIndex + 1;
    while (nextIndex < self.files.count) {
        NSString *nextName = self.files[nextIndex];
        NSString *nextLetter = [nextName substringToIndex:1];

        if (![nextLetter isEqualToString:currentLetter]) {
            break;
        }

        nextIndex++;
    }

    // 检查是否找到了有效的下一个索引。
    if (nextIndex < self.files.count) {
        // 滚动表视图以匹配下一个字母的行。
        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:nextIndex inSection:0]
                              atScrollPosition:UITableViewScrollPositionTop animated:NO];

        // 更新当前索引为新索引。
        self.currentIndex = nextIndex;

        // 取消选择当前选定的行(如果有)。
        NSIndexPath *previousSelectedIndexPath = [self.tableView indexPathForSelectedRow];
        if (previousSelectedIndexPath) {
            [self.tableView deselectRowAtIndexPath:previousSelectedIndexPath animated:NO];
        }

        // 选择指定索引处的单元格。
        NSIndexPath *indexPathToSelect = [NSIndexPath indexPathForRow:self.currentIndex inSection:0];
        [self.tableView selectRowAtIndexPath:indexPathToSelect animated:NO scrollPosition:UITableViewScrollPositionNone];
    } else {
        // 如果到达列表末尾,则重置索引为第一个元素。
        self.currentIndex = 0;

        // 滚动回表视图的顶部。
        [self.tableView setContentOffset:CGPointZero animated:NO];

        // 取消选择当前选定的行(如果有)。
        NSIndexPath *previousSelectedIndexPath = [self.tableView indexPathForSelectedRow];
        if (previousSelectedIndexPath) {
            [self.tableView deselectRowAtIndexPath:previousSelectedIndexPath animated:NO];
        }

        // 选择指定索引处的单元格。
        NSIndexPath *indexPathToSelect = [NSIndexPath indexPathForRow:self.currentIndex inSection:0];
        [self.tableView selectRowAtIndexPath:indexPathToSelect animated:NO scrollPosition:UITableViewScrollPositionNone];
    }
}

不确定是否需要,但应用程序的主场景是一个标签栏控制器。它总共有两个标签,左侧的标签在启动时显示为分割视图,而问题所在的第二个标签只是一个UITableViewController,通过storyboard中的segue呈现。

英文:

Here's a clip on YouTube showing the issue. Video

I have a UITableView that lists the files located in the app alphabetically. As there are over 1500 files, I'm trying to implement functionality to navigate by letter of the alphabet when the right button is clicked on the remote. The issue I'm having is that the TableView indeed moves to the right spot, but instead of being highlighted like it normally would, I get a lighter highlighted color, and clicking select does nothing. If I then navigate normally by pressing down, it moves it back up to the 2nd item in the list. What am I doing wrong?

- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.definesPresentationContext = YES;  // know where you want UISearchController to be displayed
self.currentIndex = 0;
[self populateTheView];
}
- (void)populateTheView {
NSBundle *bundle = [NSBundle mainBundle];
self.title = @&quot;Devo Songs&quot;;
self.files  = [bundle pathsForResourcesOfType:@&quot;pdf&quot; inDirectory:@&quot;WorshipSongs&quot;];
NSString *documentsDirectoryPath = [self.files objectAtIndex:thepath.row];
self.filenames = [[documentsDirectoryPath lastPathComponent] stringByDeletingPathExtension];
NSMutableArray *names = [NSMutableArray arrayWithCapacity:[self.files count]];
for (NSString *path in self.files) {
[names addObject:[[path lastPathComponent] stringByDeletingPathExtension]];
}
//self.files = names;
self.files = [names sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
NSLog(@&quot;FILESLOAD%@&quot;, self.files);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self becomeFirstResponder]; // Make the view controller the first responder to handle remote control events.
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
// Override pressesBegan method to detect right arrow key press event.
- (void)pressesEnded:(NSSet&lt;UIPress *&gt; *)presses withEvent:(UIPressesEvent *)event {
[super pressesEnded:presses withEvent:event];
for (UIPress *press in presses) {
if (press.type == UIPressTypeRightArrow) {
[self handleRightButtonPress];
}
}
}
- (void)handleRightButtonPress {
// Get the current name.
NSString *currentName = self.files[self.currentIndex];
// Get the current letter.
NSString *currentLetter = [currentName substringToIndex:1];
// Find the next index starting from the current index.
NSInteger nextIndex = self.currentIndex + 1;
while (nextIndex &lt; self.files.count) {
NSString *nextName = self.files[nextIndex];
NSString *nextLetter = [nextName substringToIndex:1];
if (![nextLetter isEqualToString:currentLetter]) {
break;
}
nextIndex++;
}
// Check if we found a valid next index.
if (nextIndex &lt; self.files.count) {
// Scroll the table view to the row corresponding to the next letter.
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:nextIndex inSection:0]
atScrollPosition:UITableViewScrollPositionTop animated:NO];
// Update the current index to the new index.
self.currentIndex = nextIndex;
// Deselect the current selected row (if any).
NSIndexPath *previousSelectedIndexPath = [self.tableView indexPathForSelectedRow];
if (previousSelectedIndexPath) {
[self.tableView deselectRowAtIndexPath:previousSelectedIndexPath animated:NO];
}
// Select the cell at the specified index.
NSIndexPath *indexPathToSelect = [NSIndexPath indexPathForRow:self.currentIndex inSection:0];
[self.tableView selectRowAtIndexPath:indexPathToSelect animated:NO scrollPosition:UITableViewScrollPositionNone];
} else {
// If we have reached the end of the list, reset the index to the first element.
self.currentIndex = 0;
// Scroll back to the top of the table view.
[self.tableView setContentOffset:CGPointZero animated:NO];
// Deselect the current selected row (if any).
NSIndexPath *previousSelectedIndexPath = [self.tableView indexPathForSelectedRow];
if (previousSelectedIndexPath) {
[self.tableView deselectRowAtIndexPath:previousSelectedIndexPath animated:NO];
}
// Select the cell at the specified index.
NSIndexPath *indexPathToSelect = [NSIndexPath indexPathForRow:self.currentIndex inSection:0];
[self.tableView selectRowAtIndexPath:indexPathToSelect animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}

Not sure if it is needed, but the main scene for the app is a Tab Bar Controller. It has a total of two tabs, the one on the left that shows when launched is a Split View, and the second one is the one in question here which is just a UITableViewController, and is presented via segue in storyboard.
按字母导航 tvOS UITableView
按字母导航 tvOS UITableView

答案1

得分: 1

tvOS平台上,为了导航目的,您应该使用焦点而不是选择。只需使用indexPathForPreferredFocusedViewInTableView来通知UITableView有一个焦点的索引路径,例如:

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

...
@property (nonatomic) NSIndexPath* focusedIndexPath;

@end

@implementation ViewController

...

- (void)focusRow:(NSInteger)row {
  self.focusedIndexPath = [NSIndexPath indexPathForRow:row inSection:0];
  [self.tableView setNeedsFocusUpdate];
}

#pragma mark - UITableViewDelegate

- (NSIndexPath *)indexPathForPreferredFocusedViewInTableView:(UITableView *)tableView {
  return self.focusedIndexPath;
}

@end
英文:

On tvOS platform you should work with focuses instead of selections for the navigation purposes so just use indexPathForPreferredFocusedViewInTableView to inform UITableView for a focused index path, e.g:

@interface ViewController () &lt;UITableViewDataSource, UITableViewDelegate&gt;

...
@property (nonatomic) NSIndexPath* focusedIndexPath;

@end

@implementation ViewController

...

- (void)focusRow:(NSInteger)row {
  self.focusedIndexPath = [NSIndexPath indexPathForRow:row inSection:0];
  [self.tableView setNeedsFocusUpdate];
}

#pragma mark - UITableViewDelegate

- (NSIndexPath *)indexPathForPreferredFocusedViewInTableView:(UITableView *)tableView {
  return self.focusedIndexPath;
}

@end

答案2

得分: -1

好的,小家伙,扣好安全带!我们将在你已经相当棒的代码基础上加点魔法!

你看,相比iOS,tvOS有些不同。它有自己的焦点模型,就像舞台上的魔法聚光灯,总是试图将一个界面元素(我们的演员)置于聚光灯下。现在,你已经在以编程方式滚动和选择单元格方面做得很出色,但是焦点似乎跟不上。但别担心,我们有一个绝招!它叫做“焦点指南”。

把这个焦点指南想象成我们戏剧的导演。它将帮助我们告诉聚光灯接下来应该去哪里。

首先,让我们在 viewDidLoad 方法中产生一个新的焦点指南,并为它铺设红地毯,以引导我们的聚光灯:

self.dapperFocusGuide = [[UIFocusGuide alloc] init];
[self.view addLayoutGuide:self.dapperFocusGuide];

// 好的,铺设红地毯的时候到了。
[self.dapperFocusGuide.topAnchor constraintEqualToAnchor:self.tableView.topAnchor].active = YES;
[self.dapperFocusGuide.leftAnchor constraintEqualToAnchor:self.tableView.leftAnchor].active = YES;
[self.dapperFocusGuide.widthAnchor constraintEqualToAnchor:self.tableView.widthAnchor].active = YES;
[self.dapperFocusGuide.heightAnchor constraintEqualToAnchor:self.tableView.heightAnchor].active = YES;

接下来,让我们告诉我们的导演,在戏剧开始时,哪个演员需要聚光灯:

self.dapperFocusGuide.preferredFocusEnvironments = @[self.tableView.visibleCells.firstObject];

现在,抓紧你的帽子,因为我们将要教导我们的导演一些新的技巧!我们将告诉它如何处理我们 handleRightButtonPress 方法中的剧本变化:

// 你精彩的代码的其余部分...

// 选择指定索引处的单元格。
NSIndexPath *indexPathToSelect = [NSIndexPath indexPathForRow:self.currentIndex inSection:0];
[self.tableView selectRowAtIndexPath:indexPathToSelect animated:NO scrollPosition:UITableViewScrollPositionNone];

// 现在,我们一直在等待的时刻到了。请敲起鼓来...
// 让我们告诉导演移动聚光灯!
UITableViewCell *cellToFocus = [self.tableView cellForRowAtIndexPath:indexPathToSelect];
self.dapperFocusGuide.preferredFocusEnvironments = @[cellToFocus];

现在,不要忘记告诉编译器我们都是戏剧界的人,导入 UIFocusGuide

#import <UIKit/UIFocusGuide.h>

有了这个,当你按下右按钮时,你的tableView应该会正确地聚焦在正确的单元格上了。就像你的代码和UI之间的一场精心编排的舞蹈。现在去吧,鞠躬!你赢得了它!🎅🎉🎁

英文:

Alright, buckle up kiddo! We are about to add some magic to your already quite fantastic code!

You see, tvOS is a little bit of a different beast when compared to iOS. It's got its own focus model which is like the magical spotlight on a stage, always trying to put one interface element (our actors) in the limelight at a time. Now, you've been doing a smashing job at scrolling and selecting cells programmatically, but the spotlight... err, focus isn't quite keeping up. But fear not, we've got a trick up our sleeve! It's called a Focus Guide.

Think of this Focus Guide as the director of our play. It's going to help us tell the spotlight exactly where to go next.

First, let's conjure up a new Focus Guide in our viewDidLoad method, and lay down the red carpet for it to guide our spotlight:

self.dapperFocusGuide = [[UIFocusGuide alloc] init];
[self.view addLayoutGuide:self.dapperFocusGuide];
// Alright, red carpet time.
[self.dapperFocusGuide.topAnchor constraintEqualToAnchor:self.tableView.topAnchor].active = YES;
[self.dapperFocusGuide.leftAnchor constraintEqualToAnchor:self.tableView.leftAnchor].active = YES;
[self.dapperFocusGuide.widthAnchor constraintEqualToAnchor:self.tableView.widthAnchor].active = YES;
[self.dapperFocusGuide.heightAnchor constraintEqualToAnchor:self.tableView.heightAnchor].active = YES;

Next, let's tell our director which actor needs the spotlight when the play starts:

self.dapperFocusGuide.preferredFocusEnvironments = @[self.tableView.visibleCells.firstObject];

Now, hold onto your hat, because we're about to teach our director some new tricks! We'll tell it how to handle a change in the script in our handleRightButtonPress method:

// Rest of your spectacular code...
// Select the cell at the specified index.
NSIndexPath *indexPathToSelect = [NSIndexPath indexPathForRow:self.currentIndex inSection:0];
[self.tableView selectRowAtIndexPath:indexPathToSelect animated:NO scrollPosition:UITableViewScrollPositionNone];
// And now, the moment we&#39;ve all been waiting for. Drumroll please... 
// Let&#39;s tell our director to shift the spotlight!
UITableViewCell *cellToFocus = [self.tableView cellForRowAtIndexPath:indexPathToSelect];
self.dapperFocusGuide.preferredFocusEnvironments = @[cellToFocus];

Now, don't forget to tell the compiler that we're all theater folks here, and import UIFocusGuide:

With this in place, your tableView should now be properly focusing on the correct cell when you press the right button. It's like a well-choreographed dance between your code and the UI. Now go on, take a bow! You've earned it! 🎭👏🎉

And here's it all put together...

#import &lt;UIKit/UIFocusGuide.h&gt;
@interface YourTableViewController ()
@property (nonatomic, strong) NSArray *files;
@property (nonatomic, strong) NSString *filenames;
@property (nonatomic, strong) UIFocusGuide *dapperFocusGuide;
@property (nonatomic, assign) NSInteger currentIndex;
@end
@implementation YourTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
// We&#39;re setting the stage!
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.definesPresentationContext = YES;
self.currentIndex = 0;
// Time to call the stars for a rehearsal.
[self populateTheView];
// Let&#39;s make room for our magical focus director.
self.dapperFocusGuide = [[UIFocusGuide alloc] init];
[self.view addLayoutGuide:self.dapperFocusGuide];
// Red carpet time!
[self.dapperFocusGuide.topAnchor constraintEqualToAnchor:self.tableView.topAnchor].active = YES;
[self.dapperFocusGuide.leftAnchor constraintEqualToAnchor:self.tableView.leftAnchor].active = YES;
[self.dapperFocusGuide.widthAnchor constraintEqualToAnchor:self.tableView.widthAnchor].active = YES;
[self.dapperFocusGuide.heightAnchor constraintEqualToAnchor:self.tableView.heightAnchor].active = YES;
}
- (void)populateTheView {
NSBundle *bundle = [NSBundle mainBundle];
self.title = @&quot;Devo Songs&quot;;
self.files  = [bundle pathsForResourcesOfType:@&quot;pdf&quot; inDirectory:@&quot;WorshipSongs&quot;];
NSString *documentsDirectoryPath = [self.files objectAtIndex:self.currentIndex];
self.filenames = [[documentsDirectoryPath lastPathComponent] stringByDeletingPathExtension];
NSMutableArray *names = [NSMutableArray arrayWithCapacity:[self.files count]];
for (NSString *path in self.files) {
[names addObject:[[path lastPathComponent] stringByDeletingPathExtension]];
}
self.files = [names sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
NSLog(@&quot;FILESLOAD%@&quot;, self.files);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Cue the music! The show has started.
[self becomeFirstResponder]; 
// Initial spotlight position.
self.dapperFocusGuide.preferredFocusEnvironments = @[self.tableView.visibleCells.firstObject];
}
- (BOOL)canBecomeFirstResponder {
return YES; // Of course we can! We&#39;re the star of the show!
}
// Here we keep an eye out for any new script changes (aka right arrow key press events).
- (void)pressesEnded:(NSSet&lt;UIPress *&gt; *)presses withEvent:(UIPressesEvent *)event {
[super pressesEnded:presses withEvent:event];
for (UIPress *press in presses) {
if (press.type == UIPressTypeRightArrow) {
[self handleRightButtonPress];
}
}
}
- (void)handleRightButtonPress {
// Your logic here to find the next index...
// Get the newly called star ready for the scene.
NSIndexPath *indexPathToSelect = [NSIndexPath indexPathForRow:self.currentIndex inSection:0];
[self.tableView selectRowAtIndexPath:indexPathToSelect animated:NO scrollPosition:UITableViewScrollPositionNone];
// And now, the moment we&#39;ve all been waiting for. Drumroll please... 
// The spotlight moves!
UITableViewCell *cellToFocus = [self.tableView cellForRowAtIndexPath:indexPathToSelect];
self.dapperFocusGuide.preferredFocusEnvironments = @[cellToFocus];
}
// Here you would have your other UITableViewDelegate and UITableViewDataSource methods...
@end

huangapple
  • 本文由 发表于 2023年7月31日 21:55:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76804325.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定