tvOS Focus button in a Subview - swift

I am using AVPlayerController and I have added a subview on top of AVPlayerController. As Soon the subview is displayed I want to shift focus from the player to the Subview and focus the UIButton added on top of that Subivew. I have tried preferredfocusview but its a read-only property and I cannot change it. Can anyone pelase assist. Thanks a lot.

First of all, preferredFocusView is deprecated so you should use preferredFocusEnvironments instead.
It is a computed property, which means you don't assign to it, you override it:
override var preferredFocusEnvironments: [UIFocusEnvironment] {
//if subview exists, return the subview or the button, whichever is focusable
return [subview]
//otherwise use the default implementation of AVPlayerController
return super.preferredFocusEnvironments
}
You should also call self.setNeedsFocusUpdate() and self.updateFocusIfNeeded() to request a focus update when you add the subview.

Related

Dismiss delegate for UIMenuElement Swift

I have a UIMenu on my app and I want to detect when a user taps outside(dismiss) the UIMenu. But it seems like Apple does not support any delegates by default for this action.
Because when a user taps outside I want to change the image of the button.
Sample Image
One rather hacky way I found, is to subclass UIButton and override contextMenuInteractionWillEndFor.
class MyButton: UIButton {
override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
super.contextMenuInteraction(interaction, willEndFor: configuration, animator: animator)
print("ending!")
}
}
contextMenuInteractionWillEndFor is the delegate method that is called when you dismiss a UIMenu, but if you are setting the button's menu property, the button itself will become the delegate of the UIContextMenuInteraction, and you can't set it to something else, which is why you have to subclass UIButton.
Compare this to when you are adding a context menu using addInteraction, where you have control over the UIContextMenuInteractionDelegate.

How to handle iOS 11 large title animation when using multiple container views?

I am making an app at the moment where 1 screen has a segmented control with 3 segments. Initially I had 1 table view and when you change segment I would simply change the data source/cell etc and reload the table. While this works great there is always the problem that when you change segments it will not remember your last scroll position because the table view gets reloaded.
I tried to get around this with storing offset position, rows etc but I could never get it to work like I wanted. Seems especially annoying when you have different cell types for the segments and they are self sizing as well.
I than decided to have a master view controller with the segmented control and 3 container views with their own VC and table view for each segment. I simply hide/show the correct container view when changing segments. This also works great but I have 1 problem with iOS 11 style large headers. Only the 1st container view added as a subview to the ViewControllers view manipulates the collasping/expanding of the title when you scroll.
Therefore when I change to the 2nd or 3rd container view and start scrolling I do not get the large title collapsing animation. How can I get around that?
I tried the following
1) Change Container view zPosition when changing segments
2) Move the container view to the front by calling view.bringSubview(toFront: ...)
3) Looping through the subviews and calling
view.exchangeSubview(at: 0, withSubviewAt: ...)
I believe I could remove all container views and add the one I need again and give them constraints but I wonder if there is a more straight forward solution.
Or if someone has a good solution to remember a tableViews scroll position before reloading it I would appreciate that too.
So I found an answer that seems to work for me, thanks to this great article. https://cocoacasts.com/managing-view-controllers-with-container-view-controllers/
Essentially what I did is
1) Remove the ContainerViews and Segues from the MasterViewController Storyboard.
2) Add a lazy property for each VC of the segmented control in the MasterViewController. They are lazy so that they only get initialised when you actually need them
lazy var viewController1: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
lazy var viewController2: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
3) Create an extension of UIViewController with the following 2 methods. I added them in an extension purely for code organisation as they might be reused on other ViewControllers.
extension UIViewController {
func add(asChildViewController viewController: UIViewController) {
// Add Child View Controller
addChildViewController(viewController)
// Add Child View as Subview
view.addSubview(viewController.view)
// Configure Child View
viewController.view.frame = view.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
func remove(asChildViewController viewController: UIViewController) {
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
}
4) Now in my segmented control method that gets called when you change segment I simply add the correct ViewController. The nice thing is that remove/adding them does not actually deallocate them.
func didPressSegmentedControl() {
if segmentedControl.selectedSegmentIndex == 0 {
remove(asChildViewController: viewController2)
add(asChildViewController: viewController1)
} else {
remove(asChildViewController: viewController1)
add(asChildViewController: viewController2)
}
}
5) Make sure you call the method at point 4 in ViewDidLoad so that the correct VC is added when the VC is loaded the 1st time.
func viewDidLoad() {
super.viewDidLoad()
didPressSegmentedControl()
}
This way when we remove a ChildViewController and add another one it will always be the the top VC in the subviews array and I get my nice title collapsing animation.
Another added benefit of this approach is that if you never go to a particular segment that particular VC will never get initialised, because they are lazy properties, which should help with efficiency.
Hope this helps somebody trying to do the same.
This is a horrible issue which I hope will be resolved soon, but there is another fix - although I freely admit that this is a nasty hack.
Basically, the issue only applies to the FIRST container view in the hierarchy. Add another container view to your hierarchy. Set it as hidden, then delete its segue and its target view controller just to be neat. Finally, make sure that this is the first container view in the hierarchy by dragging it to the top of the list.

Swift: Recently clicked view?

Is there a property of UIView that returns the most recently clicked (or possibly otherwise "gestured") subview?
I want to have a computed property currentPosition that simply gets a the position property (a property I've defined for my subclass of UIView) from the last-clicked view.
Right now I have an optional instance property let clickedView = UIView?, and inside my gesture handlers I say clickedView = gestureRecognizer.view
and then I have the computed instance property declared as:
currentPosition: Position? { return clickedView?.position } but it would be great to have this be simpler, and especially not have to assign clickedView = gestureRecognizer.view every time.
Possible you can play with tag value. Whenever the touch event is happened loop all the subview and set the tag to zero and set the new touched view tag to some value like 100.
touch event
->set all view tag's to zero
->set the latest view tag's to 100
Loop all the subview
->if tag value is 100 that's the latest view

How to Disable Multi Touch on UIBarButtonItems & UI Buttons?

My app has quite a few buttons on each screen as well as a UIBarButtonItem back button and I have problems with people being able to multi click buttons. I need only 1 button to be clickable at a time.
Does anyone know how to make a UIBarButtonItem back button exclusive to touch?
I've managed to disable multi clicking the UIButtons by setting each one's view to isExclusiveTouch = true but this doesn't seem to count for the back button in the navigation bar.
The back button doesn't seem to adhere to isExclusiveTouch.
Does anyone have a simple work around that doesn't involve coding each and every buttons send events?
Many Thanks,
Krivvenz.
you can enable exclusive touch simply this will stop multiple touch until first touch is not done
buttton.exclusiveTouch = true
You could write an extension for UIBarButtonItem to add isExclusiveTouch?
you can simply disable the multi-touch property of the super view. You can also find this property in the storyboard.
you could try this for the scene where you want to disable multiple touch.
let skView = self.view as! SKView
skView.isMultipleTouchEnabled = false
I have found a solution to this. isExclusiveTouch is the solution in the end, but the reason why this property didn't do anything is because you need to set it also on all of the subviews of each button that you want to set as isExclusiveTouch = true. Then it works as expected :)
It's working fine in Swift
self.view.isMultipleTouchEnabled = false
buttonHistory.isExclusiveTouch = true
In addition of #Luky LĂ­zal answer, you need pay attention that all subviews of a view you want disable multi-touching (exactly all in hierarchy, actually subviews of subviews) must be set as isExclusiveTouch = true.
You can run through all of them recursively like that:
extension UIView
{
func allSubViews() -> [UIView] {
var all: [UIView] = []
func getSubview(view: UIView) {
all.append(view)
guard view.subviews.count > 0 else { return }
view.subviews.forEach{ getSubview(view: $0) }
}
getSubview(view: self)
return all
}
}
// Call this method when all views in your parent view were set
func disableMultipleTouching() {
self.isMultipleTouchEnabled = false
self.allSubViews().forEach { $0.isExclusiveTouch = true }
}

Disable UITextField keyboard?

I put a numeric keypad in my app for inputing numbers into a text view, but in order to input numbers I have to click on the text view. Once I do so, the regular keyboard comes up, which I don't want.
How can I disable the keyboard altogether? Any help is greatly appreciated.
The UITextField's inputView property is nil by default, which means the standard keyboard gets displayed.
If you assign it a custom input view, or just a dummy view then the keyboard will not appear, but the blinking cursor will still appear:
UIView* dummyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
myTextField.inputView = dummyView; // Hide keyboard, but show blinking cursor
If you want to hide both the keyboard and the blinking cursor then use this approach:
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
return NO; // Hide both keyboard and blinking cursor.
}
For Swift 2.x, 3.x, 4.x, 5.x
textField.inputView = UIView()
does the trick
If it's a UITextField, you can set it's enabled property to NO.
If it's a UITextView, you can implement -textViewShouldBeginEditing: in its delegate to return NO, so that it'll never start editing. Or you can subclass it and override -canBecomeFirstResponder to return NO. Or you could take advantage of its editing behavior and put your numeric buttons into a view which you use as the text view's inputView. This is supposed to cause the buttons to be displayed when the text view is edited. That may or may not be what you want.
Depending on how you have your existing buttons working this could break them, but you could prevent the keyboard from showing up setting the textView's editable property to NO
myTextView.editable = NO
I have the same problem when had 2 textfields on the same view. My purpose was to show a default keyboard for one textfield and hide for second and show instead a dropdown list.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
method simply did not work as I expected for 2 textfields , the only workaround I found was
UIView* dummyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
myTextField.inputView = dummyView;
myTextField.inputAccessoryView = dummyView;
myTextField.tintColor = myTextField.backgroundColor; //to hide a blinking cursor
This will totally hide the keyboard for a target textField (DropDownList in my case) and show a default one when user switches to the 2nd textfield (Account number on my screenshot)
There is a simple hack to it. Place a empty button
(No Text) above the keyboard and have a action Event assign to it. This will stop keyboard coming up and you can perform any action you want in the handle for the button click
To disable UITextField keyboard:
Go to Main.Storyboard
Click on the UITextField to select it
Show the Attributes inspector
Uncheck the User Interaction Enabled
To disable UITextView keyboard:
Go to Main.Storyboard
Click on the UITextView to select it
Show the Attributes inspector
Uncheck the Editable Behavior
I used the keyboardWillShow Notification and textField.endEditing(true):
lazy var myTextField: UITextField = {
let textField = UITextField()
// ....
return textField
}()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
}
#objc func keyboardWillShow(_ notification: Notification) {
myTextField.endEditing(true)
// if using a textView >>> myTextView.endEditing(true) <<<
}
private void TxtExpiry_EditingDidBegin(object sender, EventArgs e)
{
((UITextField)sender).ResignFirstResponder();
}
In C# this worked for me, I don't use the storyboard.
In Xcode 8.2 you can do it easily by unchecking state "enabled" option.
Click on the textField you want to be uneditable
Go to attirube inspector on right side
Uncheck "enabled" for State
Or if you want to do it via code. You can simply create an #IBOutlet of this text field, give it a name and then use this variable name in the viewDidLoad func (or any custom one if you intent to) like this (in swift 3.0.1):
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myTextField.isEditable = false
}