tvOS - Focus particular item in a Table View - objective-c

In tvOS development - By the default the focus of the table view is the 1st item. But, how to focus particular item by default in a Table View.
For Example, By default, I want to focus the nth row of the table view.
Thanks for your help in advance.

To control the focus engine in general you have to override the methods provided by UIFocusEnvironment and/or the related UITableViewDelegate and UICollectionViewDelegate methods. This behaves much differently than controlling something like the selected row/item, in that it's a global system and not specific to a particular view.
In your particular case, it may be sufficient to implement the indexPathForPreferredFocusedViewInTableView(_:) method that is available on UITableViewDelegate (note that there is an equivalent method for UICollectionViewDelegate).
One problem I ran into while attempting to do something similar was how the default chain of preferred focus views works. Eventually I noticed this part of the documentation for UIFocusEnvironment (which all views and view controllers conform to):
By default, UIView returns itself and UIViewController returns its
root view. Returning self in a focusable view indicates that view
should be focused. Returning self in an unfocusable view causes the
focus engine to pick a default preferred focused view, by finding the
closest focusable subview to the top-leading corner of the screen.
Returning nil indicates that there is no preferred focused view.
In my case this meant that the focus engine was picking a default preferred focus view and the focus related delegate methods for my UICollectionView were not being called as I expected. If you find this is the case you may need to implement preferredFocusedView (defined in the UIFocusEnvironment protocol) on your view controller subclass and return your instance of UITableView so that the delegate methods related to focus get invoked reliably.

With TVML, it's the attribute autoHighlight="true"
(https://developer.apple.com/library/tvos/documentation/LanguagesUtilities/Conceptual/ATV_Template_Guide/TVJSAttributes.html)
Doesn't work with all templates, though, there still seem to be some bugs in the system.

First thing first,
During the view Load, set:
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [myTableView]
}
To focus to a particular IndexPath,
func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath? {
if focusAtIndexPathRow != -1 {
return IndexPath(item: focusAtIndexPathRow, section: 0)
} else {
return IndexPath(item: 0, section: 0)
}
}

Related

iOS - hidden accessory becomes visible after orientation change

In my case I'm using a UITextField as the accessory that I don't need to show all the time. I've confirmed the change happens after the orientation notification events fire. I guess a hack would be to resize the accessory to zero height, but I'm reticent to do this.
Wondering if anyone has encountered this and found a solution?
Have entered a bug report and provided a sample project. For those with higher privileges, it is searchable on bugreport.apple.com as ID 16771757. I have also copied it to a Dropbox account accessible as https://www.dropbox.com/s/o28vo04ig3yhgz6/ID16771757.zip.
Thank you for reading.
iOS calls such methods for input accessory view instance:
[inputAccessoryView setAlpha:1]; when owner of accessory view becomes first responder (internal method call -[UIPeripheralHost(UIKitInternal) executeTransition:]);
[inputAccessoryView setHidden:NO]; when interface rotation finished (internal method call -[UIPeripheralHost finishRotationOfKeyboard:]);
That's why your input accessory view becomes visible after interface rotation event.
Solution depends on behaviour that you expect:
Let's imagine that input accessory view height = 44 ->
Now you hide input accessory view and set owner as first responder:
If you expect inputAccessoryView.frame.size.height equals 0 then solution for hiding input accessory view is set it to nil: inputAccessoryView = nil;
If you expect inputAccessoryView.frame.size.height equals 44 then solution for hiding input accessory view is override setHidden: method for it:
- (void)setHidden:(BOOL)hidden {
[super setHidden:self.customIsHiddenFlag];
}
where customIsHiddenFlag property that you need use for implementing logic of showing/hiding accessory view;
or override setAlpha: method:
- (void)setAlpha:(CGFloat)alpha {
[super setAlpha:(self.customIsHiddenFlag ? 0 : 1)];
}
These solutions work for iOS 7.
For iOS 6 you could use your base solution inputAccessoryView.hidden = YES and it works because iOS doesn't call setHidden:NO automatically when interface rotation is fired.
It seems that you are right and it's a bug cause of different behaviour on iOS6 and iOS7. If Apple wants to show input accessory view forcedly then they should call setHidden:NO not only after interface rotation but also when owner becomes first responder.
From Apple's documentation on inputAccessoryView:
… Subclasses that want to attach custom controls to either a system-supplied input view (such as the keyboard) or a custom input view (one you provide in the inputView property) should redeclare this property as readwrite and use it to manage their custom accessory view. [emphasis mine]
So the correct way to hide the accessory view would be redeclaring the property as readwrite:
#property (nonatomic, readwrite) UIView *inputAccessoryView;
removing the accessory view from superview and setting the property to nil when appropriate:
- (IBAction)hideAccessoryView:(UIButton *)sender
{
[self.inputAccessoryView removeFromSuperview];
self.inputAccessoryView = nil;
}
This is correct with regard to the docs but if you look at the view hierarchy, there's a UIPeripheralHostView (UIKit private class) that does not change its size. This most likely means that throwing out the accessory view will not be reflected by keyboard size — it'll stay the same. Keep this in mind if you plan to calculate any offsets to adjust to on-screen keyboard.
That said, the best way for you to move forward might be using a completely transparent view as the accessory view and have your custom view (UITextField in this case) as a subview. That way you will get both complete control over your custom view and consistent behaviour of your app on current and future versions of iOS.
Edit:
Here's a screenshot showing a slightly modified version of your bug report app with UIPeripheralHostView highlighted:
You can see how size of the view stays the same after the accessory view has been removed.
When you add an accessory view, you "pass" it to the system for layout. It is more than likely, when Apple performs layout on the keyboard view, it also layouts the accessory view and sets it to visible. Setting the accessory as hidden can also have other side effects, such as the keyboard height being incorrectly calculated, thus causing incorrect inset calculation.
From my experience, it is best to remove the accessory and add it again when necessary. Resizing the accessory view will cause other issues related to keyboard size. If you want to hide and show quickly, subclass the view that includes the accessory view, and implement internally the setting and removing of accessory view.

Scroll NSTableView on load

I have a NSViewController containing a view-based table view. When I initialise the view controller and put its view on screent the table view is scrolled to the bottom. I would prefer instead to have the table view scrolled to the top.
I know the method -scrollRowToVisible: but I can't figure where to call it in order for the table view to first appear scrolled to the top. I tried inserting it at the end of the -loadView method of the NSViewController but obviously it doesn't work as the table view hasn't even loaded its rows yet at that point.
What would it be the best place to place the call to the -scrollRowToVisible: method? Or alternatively, is there a property to make the table view show it's first row instead of the last?
I think this is the way to go..
-- you should call scroll once all the item / rows of a table got loaded/displayed in the tableview
-- Somewhere you would do reloadData, Possibly in the awake from nib, This is not a kind of blocking function, it will call some of the UI Delegate function, like
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)aTableColumn byItem:(id)item
Perhaps you should check here for the last item and there might be some Delegate function in the NSTableViewUIDelegate, which tells you when an Item got loaded/displayed in the Table, there you can check for the last item , it means all the table item got loaded then its safe to call scrollToVisible
It's an old question, but for the record, since I ran into this myself today:
The NSScrollView of your table view starts out scrolled to the bottom, most likely because the scroll view restores its last scroll position after closing the window last time.
This is part of the User Interface Preservation / State Restoration feature.
To avoid that the scroll view restores the scroll position (in fact, anything at all), you can subclass NSScrollView and subclass these:
class NonPreservingScrollView: NSScrollView {
override func encodeRestorableState(with coder: NSCoder) {
// Nothing
}
override func restoreState(with coder: NSCoder) {
// Nothing
}
}
Maybe a bit cleaner than trying to track when reloading data has finished and the scroll view won't move when the window loads...

Get UIViewController to reload different UIView programmatically

I am overriding the loadView method within a UIViewController as follows:
-(void)loadView
{
NSLog(#"HPSFormController loadView starting");
HPSFormView* viewForThisController = [ [ HPSFormView alloc ] initWithFrame:_frame ] ;
self.view = viewForThisController;
}
When a certain button is pressed within the view then the same UIViewController gets control again and at this point I wish to completely change the view that the controller is showing. As follows:
-(void)buttonTapped
{
ABCFormView* newview = [ [ ABCFormView alloc ] initWithFrame:_frame ] ;
self.view = newview;
}
However, the buttonTapped method does not load the second view. A completely blank view is shown instead.
How do I get the view controller to reload a completely different view when the button is pressed?
However, the buttonTapped method does not load the second view. A completely blank view is shown instead.
Is it not possible that the problem is in the way you create ABCFormView? I mean, it seems that the original view is replaced by an empty view, so check how the latter is created...
EDIT AFTER YOUR COMMENT:
if you say that the view is "created within a viewDidLoad method within the view controller", then you should instantiate your view controller:
#property (...) ABCFormViewController* newviewController;
....
-(void)buttonTapped
{
self.newviewController = [ [ ABCFormViewController alloc ] init] ;
self.view = newviewController.view;
}
keep in mind that newviewController must be around as long as you are using its controlled view, otherwise you will get a crash. That is the reason why I store its reference in a property.
Obviously you can't get your new view visible by simply setting self.view = newView; because the newView has never been added as a subview to any other views yet - i.e. not in the window.
If you need to switch to a different view, you should probably add APSFormView as a subview to your viewcontroller.view, and when you need to switch, remove APSFormView from superview then add ABCFormView as a new subview to viewcontroller.view.
If your loadView implementation needn't do much else, it may be better to use the storyboard to set it up initially. It is easy to miss, but you can specify in the storyboard that the view should be of a custom type (in the "identity inspector" with the view selected). Further, it may be worth evaluating why a completely different class of view is necessary for the same view controller instance; to me this may be a red flag regarding the application design. You may be better served by a flow between two view controllers, or else write some state-changing logic in this custom UIView-extending class. The decision for me would be made based on the model being represented by the views, along with which behaviors each is designed to facilitate.
If the models are different (i.e. your first view shows a list of accounts, second shows one account detail), or if the behaviors are significantly different (i.e. the first is viewing an account and the second is creating a new one), then I would use two distinct view controllers.
If the models and behaviors are similar, and the style should change, then I would likely write state-changing code in the custom view class to rearrange things, etc.
If you are coming from a different platform, it can seem silly at first, but we really do throw around view controllers without much hesitation. They are elegantly handled by the framework, and are designed to manage "a screenful of content" and be easily swapped for another screenful.
It is hard to tell without knowing what is inside ABCFormViewController. I had some timing issues once on a view controller which I just needed to generate the view because I wanted to capture its content to create a pdf file (its a view that never gets displayed onscreen). In that case I needed to insert a code like this:
[newviewcontroller.view setneedsrefresh];
Before I do
otherVC.view = newviewcontroller.view;
Otherwise I get a blank page.
I believe I get this because by the time everything is sorted out ARC deallocates newviewcontroller so the view is nil. In your case this may not be the problem. Is there a reason why you need a 2nd and 3rd view controller to put into your view because a much simpler way of doing this is to just transfer control to the other view controllers via modal, pop view or a navigationController. Another more usual way is to create multiple views in your XIB and then just load it into a blank view instead of creating view controllers for each of them.

Forwarding drag & drop event to parent view

I have an application where I have one custom view which is derived from NSView.
Within this view, there are several custom subviews, which are also derived from NSView.
I want to implement a drag and drop behavior which allows URLs to be dropped onto the views. Everything is already working for the main view.
So, actually I would have to implement dragging behavior handlers on the child-views and the parent-view class.
The thing is, that I don't want to copy the complete handling code to all the child-views to make them also accept drag events. So I thought that it would be the best way to just let them forward all drag events to the parent view.
Is this possible somehow?? Not sure if I can somehow set this up with the responder-chain maybe?
Any tips are highly appreciated!!
Thanks in advance.
I faced a similar issue where I wanted anything dropped in a view, or any of it's subviews to be processed by the view, but the calls never got there.
After some research I found this answer to be the most helpful: https://stackoverflow.com/a/7389711/327471
Essentially if a view is not registered to receive any drag events it should pass that information up to it's parent view automatically. So in my case I ended up with something like this:
NSArray *subviews = [self.view subviews];
for (NSView *aSubview in subviews) {
[aSubview unregisterDraggedTypes];
}
Of course you can be more precise than that, and make sure to only check subclasses of a certain type or whatever parameters you want. But ultimately the key was unregistering the problem subview from it's dragged types.
I hope this helps.
If the subviews are used for display only and don't require any user interaction, you can override -hitTest: in the parent view like so:
- (NSView *)hitTest:(NSPoint)aPoint
{
NSView* hitView = [super hitTest:aPoint];
if(hitView)
return self;
return nil;
}
This makes the parent view receive all mouse events.
Still works XCode 10.1. Swift 4.2. under 10.14.4 Beta (18E184e).
// MARK: - ViewController lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.view.subviews.forEach { $0.unregisterDraggedTypes() }
}
There's probably a better way, but you could put your dragging protocol implementation in a category, rather than in the view directly, and include that category in each of the views.

Objective C: Can I set a subview to be firstResponder?

I have a situation whereby I am adding a view from another viewcontroller to an existing viewcontroller. For example:
//set up loading page
self.myLoadingPage = [[LoadingPageViewController alloc]init ];
self.myLoadingPage.view.frame = self.view.bounds;
self.myLoadingPage.view.hidden = YES;
[self.view addSubview:self.myLoadingPage.view];
Is it possible to set 'self.myLoadingPage' to be the first responder? This is the case whereby the loadingpage view size does not cover the entire size of the existing view and users can still interact with the superview (which is not the desired behaviour). I want to just enable the subview in this case.
When I had a similar problem, I made an invisible UIView that covered the entire screen, I added the large invisible UIView on top of the main view and made the loading view a subview of the invisible UIView.
The simplest solution is to override hitTest method in your loading view to return TRUE. This top view is first in the responder chain, the hitTest method gets called which NORMALLY returns TRUE if the point is within the view and will therefore be handled, returning TRUE regardless means you get the touch event and effectively block the message being resent to the next responder.
Interesting question. I found a similar post with a quote from the Apple Developer Forums on this issue:
To truly make this view the only thing
on screen that can receive touches
you'd need to either add another view
over top of everything else to catch
the rest of the touches, or subclass a
view somewhere in your hierarchy (or
your UIWindow itself) and override
hitTest:withEvent: to always return
your text view when it's visible, or
to return nil for touches not in your
text view.
This would seem to indicate there isn't a terribly straightforward solution (unless there was an API change regarding this made after October, 2010.)
Alternatively, I suppose you could go through all the other subviews in your superview and individually set their userInteractionEnabled properties to NO (but that would probably prove more cumbersome than the quoted solutions).
I would love to see other ways to allow this.