Remove PreferredFocusedView tvOS - focus

I have a code in Objective C which I have used a PreferredFocusedView Constraints then I wonder how can I remove this later without changing the view,
when I say I wanna remove it means I wanna remove this virtual places (in green and blue ) totally and not only remove the FocusGuid layout from the view
I already managed to remove the FocusGuide by using
MyfocusGuide.preferredFocusedView = [self preferredFocusedView];
But still, those goats buttons (green and blue) exist and in action every time I move to their location.

The solution I have found is to use the new array UIFocuseEnvironment with tvOS 10.0 that make it easy for me
- (NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments //Called By [self setNeedsFocusUpdate]
{
if (CONDITION)
{
return #[BUTTON1, BUTTON2];
}
else
{
return nil;
}
}

Related

tvOS: Focus not moving correctly

I have a UIView with two buttons on it. In the MyView class I have this code:
-(BOOL) canBecomeFocused {
return YES;
}
-(NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {
return #[_editButton, _addButton];
}
-(IBAction) editTapped:(id) sender {
BOOL editing = !tableViewController.editing;
[_editButton setTitle:editing ? #"Done" : #"Edit" forState:UIControlStateNormal];
_addButton.hidden = !editing;
[tableViewController setEditing:editing animated:YES];
}
The basic idea is that the user can move the focus to the edit button, which can then make the Add button appear.
The problem started because every time I tapped the edit button, focus would shift to the table view. I would actually like it to move to the Add button. I also want it so that when editing it deactivated, the edit button keeps the focus. but again it's shifting down to the table view.
So I tried the above code. This works in that focus can move to the view and on to the button. But once it's there, I cannot get it to move anywhere else.
Everything I've read says just override preferredFocusEnvironments but so far I've not been able to get this to work. Focus keeps going to a button then refusing to move anywhere else.
Any ideas?
If anybody is facing this issue, Just check if you are getting the following debug message printed in the console.
WARNING: Calling updateFocusIfNeeded while a focus update is in progress. This call will be ignored.
I had the following code :
// MARK: - Focus Environment
var viewToBeFocused: UIView?
func updateFocus() {
setNeedsFocusUpdate()
updateFocusIfNeeded()
}
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if let viewToBeFocused = self.viewToBeFocused {
self.viewToBeFocused = nil
return [viewToBeFocused]
}
return super.preferredFocusEnvironments
}
I was calling the updateFocus() method multiple times while viewToBeFocused was either nil or some other view. Debugging the focus issues mainly between transition is really difficult. You should have patience.
Important to note: This depends on your use case, but if you want to
update the focus right after a viewcontroller transition (backward
navigation), You might have to set the following in viewDidLoad:
restoresFocusAfterTransition = false // default is true
If this is true, the view controller will have the tendancy to focus the last focused view even if we force the focus update by calling updateFocusIfNeeded(). In this case , since a focus update is already in process, you will get the warning as mentioned before at the top of this answer.
Debug focus issue
Use the following link to debug the focus issues: https://developer.apple.com/documentation/uikit/focus_interactions/debugging_focus_issues_in_your_app
Enable the focus debugger first under Edit scheme > Arguments passed on launch:
-UIFocusLoggingEnabled YES
This will log all the attempts made by the focus engine to update the focus. This is really helpful.
You can override the preferredFocusEnviromnets with the following logic:
-(NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {
if (condition) {
return #[_editButton];
}
else {
return #[_addButton];
}
}
After setting it, you can call
[_myView setNeedsFocusUpdate];
[_myView updateFocusIfNeeded];
The condition could be BOOL condition = tableViewController.editing; or sg like that.
If that now works, you can call it with a delay (0.1 sec or so).

UICollectionView iOS 9 issue on project with RTL languages support

It seems like Apple's new feature of auto-flip interface on RTL languages cause problems when using UICollectionView.
I used constraints of type Trailing/Leading for the collection view and they switched their values, as they should, on RTL language.
The problem is that the data actually presented is of the last indexPath in the collection's data source but the UIScrollView.contentOffset.x of the first cell is 0.
A proper behaviour would have been one of the following:
Displaying the first indexPath correctly and switching the direction of the scroll (to the right) - Best option
Not flipping the UI/Constraints so the presented-data / indexPath / scrollView.contentOffset.x will be synchronised - Option that disabling the RTL support.
Presenting cell and data of the last indexPath but fixing the scrollView.contentOffset.x to represent the last cell position also.
I guess Apple might fix it sometime in the future but meanwhile we'll have to use workarounds like reversing array and/or scrolling to the last object.
I was in a similar situation and found a solution for this. If you are using swift, add the following snippet to your project, and it will make sure that the bounds.origin always follows leading edge of the collection view.
extension UICollectionViewFlowLayout {
open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
return true
}
}
If you are using Objective-C, just subclass the UICollectionViewLayout class, and override flipsHorizontallyInOppositeLayoutDirection, and return true. Use this subclass as the layout object of your collection view.
I am late but if you don't want to create an extension because it will affect all the collection View in our app. Simply create your own custom class ie.
class CustomLayoutForLocalization : UICollectionViewFlowLayout{
open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
return true
}
}
To use this class:
// your way of deciding on whether you need to apply this layout may vary depending on use of other tools like LanguageManager_iOS to handle multi-language support
if myCollectionView.effectiveUserInterfaceLayoutDirection == .rightToLeft {
let customLayout = CustomLayoutForRTL()
// if your elements are variable size use the following line
customLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
// if you want horizontal scroll (single line)
customLayout.scrollDirection = .horizontal
myCollectionView.collectionViewLayout = customLayout
}
There is one common solution for that problem that works for me, follow below steps to overcome that problem,
Give the auto layout constraint as per your requirement and then from attribute inspector change the semantic control property of the collection view to Force right-to-left from the storyboard.
Then open storyboard as source code and find for the “leading” attributes of your relevant collection view and replace that with the “left” and same for the “trailing” replace that with the “right”. Now you almost done.
now that will give you result as per your requirement.
import UIKit
extension UICollectionViewFlowLayout {
open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
return UIApplication.shared.userInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.rightToLeft
}
not pretty though simple math does the trick. (for horizontal collectionview)
- (void)switchSemanticDirection:(UISwitch*)sender {
//TEST switch the semantic direction between LTR and RTL.
if (sender.isOn) {
UIView.appearance.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
} else {
UIView.appearance.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
}
[self.myContent removeFromSuperview];
[self.view addSubview:self.myContent];
//reload your collection view to apply RTL setting programmatically
[self.list reloadData];
//position your content into the right offset after flipped RTL
self.list.contentOffset = CGPointMake(self.list.contentSize.width - self.list.contentOffset.x - self.list.bounds.size.width,
self.list.contentOffset.y);
}

Check if one method has been run in a different method

I'm writing some code in Xcode for an iPhone app and I want to be able to detect if a method has been run (i.e. a button was pressed, causing that method to run) in another method (I want to use an if statement so that if the button was pressed it will do this, but if it wasn't then it will do something else).
There is no has_method_been_run() function but you can check to see if the state was changed.
For example say method button_clicked() calls method change_font_to_blue(). In this case you can check to see if the font is blue and there for the method was called.
That's a very basic example of course but you could check any number of variables / the state of the UI to see if it was changed.
OR you can add a boolean to an object and just set it to true when you execute you're method that you're watching.
If your example is simply a button press I would change the UIButton's selected state
- (IBAction)buttonSelected:(UIButton *)sender {
sender.selected = YES;
// OR:
// self.myButton.selected = YES;
}
- (void)otherMethod {
if (self.myButton.isSelected) {
// button has been run through the selector method
// if you want, reset button's selected state
self.myButton.selected = NO;
} else {
// button has NOT been run through the selector method
}
}
This is a simple idea and by default the selected state of a UIButton is not visually different. If you want it to be visually changed then you can simply go into IB and change the visuals there for the selected state (including the title):
Then when the button is set to selected (self.myButton.selected = YES;) it will automatically change what the button looks like!

UIAutomation: Check if element exists before tapping

We have a iPad app which includes a two-column news reader. The left view contains the list of news of which some link directly to a news and some push another view controller with another list of news. This will also cause a UIButton to be set as the leftBarButtonItem of the navigation bar. If we are on first level, a simple image that cannot be tapped will be the leftBarButtonItem.
My goal is now to have a test that taps every news on the first level. If a news leads to a second level list, it should tap the UIButton in the navigation bar.
How can I check, if the leftBarButtonItem is "tappable"? Since it can be either an image or a button, just calling navigationBar().leftButton().tap() will lead to an error if it's an image.
I'm also using the tuneup library if that's any help.
Almost all elements in UIAutomation could be tapped. It does not matter if it is an Image, View or a Button. You will get an error in case an object you are trying to tap is invalid.
How to check:
if ( navigationBar().leftButton().checkIsValid() )
{
navigationBar().leftButton().tap();
}
else
{
//do what you need.
}
or you can check if an object you are trying to tap is a button, for example (not the best way but it works):
if ( navigationBar().leftButton().toString() == "[object UIAButton]" )
{
navigationBar().leftButton().tap();
}
else
{
//do what you need.
}
checkIsValid() is available for all UI elements. It will return true if an object exists.
toString() will return [object UIAElementNil] if element is invalid or will return [object UIAButton] or [object UIAImage].
Also try to use Apple documentation:
http://developer.apple.com/library/ios/#documentation/ToolsLanguages/Reference/UIAElementClassReference/UIAElement/UIAElement.html
You can simply use
if (navigationBar().leftButton().exists)
{
navigationBar().leftButton().tap();
}
else
{
//do what you need.
}

How to implement setNeedsReload / setNeedsRefresh

I've been wondering about extending UIView with a sort of setNeedsRefresh method of my own - a method that behaves like setNeedsLayout and setNeedsDisplay but with the purpose of refreshing/reloading visible data, hiding views, etc. My goals would be to:
Be able to detach methods (particularly setters) from having to instantly refresh views.
Be able to flag a view for refresh rather than refreshing it manually (in order to optimize code execution and have a cleaner code architecture).
I was hoping to be able to extend UIView in such a way that you can call setNeedsRefresh on any view, and have it execute refreshView or some similar method before redrawing happens.
PS: is the best way to handle this through setNeedsLayout + a refresh flag?
Update #1: The following is an example of what I want to be able to do:
-(void)setAvailableForPurchase:(BOOL)availableForPurchase
{
if (_availableForPurchase != availableForPurchase)
{
_availableForPurchase = availableForPurchase;
[self setNeedsRefresh];
}
}
And then:
-(void)refreshView
{
if (self.isAvailableForPurchase)
{
self.someView.image = someImage;
self.someButton.enabled = yes;
// more code, changes several objects
}
else
{
// some other code...
}
}
This example is a very simple one on purpose to better express my requirement - the reasons why I need/want to do this are listed in my initial question.

Resources