How to move focus at button in collectionview cell in tvos application? - ios

I am working on apple TV application. In my app, I have made a screen which has a collection view. In that case, I am able to move focus at collection view cell but not able to move focus to the button which is in collection view cell so can anyone help me to solve this issue ?
please give me an answer if anyone knows this answer.

I am able to solve this problem by adding below methods in collectionViewCell subclass.
In CollectionViewCell Subclass
override var preferredFocusEnvironments: [UIFocusEnvironment]{
// Condition
}
override func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool {
// Condition
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
// Condition
}
you can see more at this link:
enter link description here.

I think this page will guide to achieve what you want. https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/WorkingwiththeAppleTVRemote.html#//apple_ref/doc/uid/TP40015241-CH5-SW4
It give good explaination of how focus engine will decide which object should get next focus. Below is great explanation step by step at above link.
Here is an example showing how focus might be determined:
The focus engine asks the root window for its preferredFocusedView,
which returns its root view controller’s preferredFocusedView
object.
The root view controller, a tab view controller, returns its
select view controller’s preferredFocusedView object.
The select view controller overrides its preferredFocusedView method to return a specific UIButton instance.
The UIButton instance returns self(the default), and is focusable, so it is chosen by the focus engine as the next focused view.
You have to override the preferredFoucsedView property of your UIView or UIViewController.
override weak var preferredFocusedView: UIView? {
if someCondition {
return theViewYouWant
} else {
return defaultView
}
}
Thanks to Slayter's answer

Related

Why is my UI Button working but not animating or indicating to the user?

I have added a UI Button inside of a stack view which is inside of a table view in my storyboard. When I click on my button the correct output is printed in my debugger console but there is no indication in the app that the button has been clicked (no default animation). I have tried looking at my view hierarchy and changing all of the parent views to clip to bounds. Any idea why the button is functioning but not being animated to the user?
The quick fix to your problem is to set delaysContentTouches = false for your table view.
According to the Apple Docs,
If the value of this property is true, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is false, the scroll view immediately calls touchesShouldBegin(_:with:in:). The default value is true.
See the class description for a fuller discussion.
Alternatively, if you have subclassed the UIScrollView, you can get the same thing done by overriding the following function,
class MyScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return type(of: view) == UIButton.self
}
}

UIStoryboardSegue animates property in subclass

I have a UIStoryboardSegue subclass for replacing current view controller with next view controller.
As we have a Animates property in interface editor, I want to access this property in the subclass.
My code is following:
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
var viewControllers = source.navigationController?.viewControllers.dropLast() ?? []
viewControllers.append(destination)
source.navigationController?.setViewControllers(viewControllers.map {$0}, animated: true) // I dont want this `true` to be hardcoded
}
}
As per comments in UIStoryBoardSegue class
The segue runtime will call +[UIView setAnimationsAreEnabled:] prior
to invoking this method, based on the value of the Animates checkbox
in the Properties Inspector for the segue.
So obviously you can read the value of animate check box by using
UIView.areAnimationsEnabled
So in my custom segue
class MySegue: UIStoryboardSegue {
override func perform() {
debugPrint(UIView.areAnimationsEnabled)
}
}
This prints false if animate checkbox is unchecked or true if it is checked :)
So in your case
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
var viewControllers = source.navigationController?.viewControllers.dropLast() ?? []
viewControllers.append(destination)
source.navigationController?.setViewControllers(viewControllers.map {$0}, animated: UIView.areAnimationsEnabled)
}
}
I hope whats happening is already clear, incase you still have doubt, here is the explanation, iOS checks the animates checkbox value and uses it to set whether animations are enabled or not by calling setAnimationsAreEnabled with the value of animates check box in interface prior to calling perform() method.
So when the control reaches inside perform you can be assured that iOS has already read the value of animates check box and used it to set setAnimationsAreEnabled all you have to do now is to ask areAnimationsEnabled to get the value of animates check box.
So that should provide you the value of animates checkbox :)
Hope it helps :)
You shouldn't need a UIStoryboardSegue subclass for this. The docs state "You can subclass UIStoryboardSegue in situations where you want to provide a custom transition between view controllers". This means that a replacement without without any animation isn't a custom transition, thus shouldn't use a segue subclass.
The correct way to do replacement is to use a Show Detail (e.g. Replace) segue and inside the parent view controller that is managing the child view controllers implement the method showDetailViewController and replace the children, e.g.
#implementation DetailNavigationController
- (void)showDetailViewController:(UIViewController *)vc sender:(id)sender{
[self setViewControllers:#[vc] animated:NO];
}
If you didn't know, the Show Detail segue (after magically instantiating the destination view controller) has a perform method that just calls showDetailViewController on self, and the base UIViewController implementation searches up the view controller hierarchy looking for one that overrides showDetailViewController, so you can intercept it and perform your custom code, before say it goes up to another parent that might implement it also like a split view.

Apple TV force focus another view

I'm working on Apple TV project. The project contains tab bar view controller, normally the tab bar will be appeared when swiping up on remote and hidden when swiping down. But now I reverse that behavior and I want to force focus another view when swiping up(normally focus on tab bar). Any way to do that? Thank you.
In your UIViewController, override shouldUpdateFocusInContext. If you detect an upward navigation into the tab bar, return false to prevent focus from reaching the tab bar. Then use a combination of preferredFocusEnvironments + setNeedsFocusUpdate to redirect focus somewhere else:
override func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool {
if let nextView: UIView = context.nextFocusedView{
if ( context.focusHeading == .up && nextView.isDescendant(of: tabBar) ){
changeFocusTo(myView)
return false
}
}
}
internal var viewToFocus: UIView?
func changeFocusTo(_ view:UIView? ){
viewToFocus = view
setNeedsFocusUpdate()
}
override var preferredFocusEnvironments: [UIFocusEnvironment]{
return viewToFocus != nil ? [viewToFocus!] : super.preferredFocusEnvironments
}
This is a generally useful technique for customizing focus updates. An alternative technique is to use UIFocusGuide. You could insert a focus guide underneath the tab bar or surround the tab bar with a focus guide to redirect focus. Though focus guides are useful for simple cases, I have generally had better results using the technique I am describing instead.
I got the same issue with focus of UITabbarController before and I found the solution in Apple Support
Because UIViewController conforms to UIFocusEnvironment, custom view
controllers in your app can override UIFocusEnvironment delegate
methods to achieve custom focus behaviors. Custom view controllers
can:
Override the preferredFocusedView to specify where focus should start
by default. Override shouldUpdateFocusInContext: to define where focus
is allowed to move. Override
didUpdateFocusInContext:withAnimationCoordinator: to respond to focus
updates when they occur and update your app’s internal state. Your
view controllers can also request that the focus engine reset focus to
the current preferredFocusedView by callingsetNeedsFocusUpdate. Note
that calling setNeedsFocusUpdate only has an effect if the view
controller contains the currently focused view.
For more detail, please check this link
https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/WorkingwiththeAppleTVRemote.html#//apple_ref/doc/uid/TP40015241-CH5-SW14

Swift - Access IBOutlet in other class

I have a UIView with a TableView and a Button (Big Button). The TableView has a custom Cell. In this cell there is an "Add" button. I want to animate the first button when the user makes click on the Add button.
This is my schema:
This is my code:
class ProductsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet var bigButton: UIButton! <- I WANT TO ANIMATE THAT BUTTON
}
ProductCell class
class ProductCell: UITableViewCell {
#IBAction func addProduct(sender: AnyObject) {
//I WANT TO ACCESS THE BIG BUTTON FROM HERE
}
}
Screen example of my app
I've tried to get the parent controller or the superview to get the IBOutlet but the app is crashing allways
Add block properties to your cells which lets them notify your view controller when they have been clicked. In your view controller block code, you can then access the big button.
See my answer to a similar question. Simply replace the switch example with your button. So replace UISwitch with UIButton.
How can I get index path of cell on switch change event in section based table view
So rather than have the cell try and talk to another cell/button, have the cell notify the controller which can then manage the big button changes.
Although I made a comment about using alternate methods you could also employ a strategy below based on updates to a property stored in the current view controller class. You could just as well use property observation on the ProductsViewController but I assume you'd like to keep OOP focused and reduce the size of your controller.
Subclass the ViewController
One could subclass an existing UIViewController and then create a property in the super class that deals with the value that was changed (row tapped). In that subclass you could then do some animation. Because you would be subclassing you continue to obtain all the benefits and methods defined in your existing controller. In your identity inspector point your Class to the new subclass and create any functional updates to your UI using animation.
class ProductsViewController:... {
var inheritedProperty:UIView = targetView {
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
}
}
class AnimatedProductsViewController:ProductsViewController {
override var inheritedProperty:UIView {
//do something interesting if the property of super class changed
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
//you might want to call this method like so
// didSet { animate(newValue) }
}
func animate (view: UIView){
//do animation routine using UIView animation, UIDynamics, etc.
}
}
Property Observation
Whenever the didSelectCell... method is called just set a value to the inheritedProperty. Then add the property observers (see sample code) and react when the property changes (maybe pass a reference to the view you want to animate).
For example: Within the property observer you can just take that view and pass it to your animator function (whatever is going to do the animation). There are many examples on SO of how to animate a view so just search for (UIView animation, UIDynamics, etc).
The normal benefits of separation are encapsulation of functionality and reuse but Swift also guarantees that each set of property observers will fire independently. You'd have to give some more thought to this as to its applicability in this use case.
Do all this things in your viewController
Add target Method to cell's add button in cellForRowAtIndexPath Method
Like This
cell.add.addTarget(self, action: "addProduct:", forControlEvents: UIControlEvents.TouchUpInside)
Define method
func addProduct(button:UIButton)
{
// do button animation here
}

Launch Watch App into middle view

Basically, my app is laid out in the page format and I would like it to launch into the middle of the three pages. There is no way of setting a previous page segue, so I have been trying to do it in code.
I have the main view set to the first view, and I have tried a variety of methods to segue to the middle view as soon as the app is launched.
Here is the two ways I tried:
if segueCheck == true {
self.pushControllerWithName("budget", context: self)
self.presentControllerWithName("budget", context: self)
segueCheck = false
}
The first presents the view, but as a completely separate view, and the second replaces the first view with the middle view.
Does anyone know how I can launch into the middle view and allow the user to swipe left and right of it?
Thanks.
WKInterfaceController's becomeCurrentPage() should be what you're looking for.
Let's create a new class for the center view controller, CenterPageViewController, and change its initWithContext: method as follows
import WatchKit
class CenterPageViewController: WKInterfaceController {
override init(context: AnyObject?) {
super.init(context: context)
super.becomeCurrentPage()
}
}
Now let's set the Custom Class for the middle page in your storyboard to CenterPageViewController
and finally hit run.
You won't be able to get rid of the initial transition from the left page to the center page, but the app will finally begin on the middle page.
Update Swift 3.0
class CenterPageViewController: WKInterfaceController {
override init (){
super.init()
super.becomeCurrentPage()
}
}
This will works...!!!
Thanks
The new way to do this in watchOS 4 and higher is:
WKInterfaceController.reloadRootPageControllers(withNames:
["Controller1" "Controller2", "Controller3"],
contexts: [context1, context2, context3],
orientation: WKPageOrientation.horizontal,
pageIndex: 1)
Now you don't get the annoying animation when using becomeCurrentPage() when you want to start with the middle page.

Resources