How to detect button highlight on Apple TV in Swift - swift

I am making a simple app that will show the user some information when they hover over a button. I have looked all over for an answer for this but it seems that no one has wondered this yet. I know it is possible to detect button highlights because I have seen it in some apps I downloaded on my Apple TV. Here is basically what I'm aiming for:
#IBAction func firstButton(_ sender: Any) {
//This function would be called when the first button is highlighted/hovered over
label.text = "Hi there"
}
#IBAction func secondButton(_ sender: Any) {
//This function would be called when the second button is highlighted/hovered over
label.text = "How are you?"
}
I know that just creating an IBAction func won't do the trick, but I am just using this as an example of what I want to do.
So is there a way to detect button highlights/button hovering and how?
Thanks in advance. Any help is appreciated.

By hover, I think you mean "is the button in focus". There are a couple ways to tell if a UI element is in focus.
For UIViewController you can override didUpdateFocus which provides context about what happening in the focus engine.
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if context.nextFocusedView == myButton {
print("My button is about to be focused")
}
else {
print("My button is NOT in focus")
}
}
Or in a custom UIView (ie. a customUIButton) you can check the isFocused property.
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
print(isFocused)
}

Related

How to undo UITextField edits in Swift

I'm sure there's a simple answer to this, but I'm pretty noob to programming and have searched extensively here and on the few Undo tutorials I could find.
I have two buttons and a UITextField with a number in it. The buttons increment the number in the textfield up or down by 1 respectively. I have several more sets of these two buttons and a textfield, each representing something different. Then I have undo and redo buttons that undo the changes to the text fields caused by pressing the buttons. This all works great.
So now I'm trying to add the ability to undo changes from editing the text field directly with the number pad, so that it goes into the same undo stack and can be undone and redone from the same undo and redo buttons.
Here's a snippet of the code that controls the undo and redo of the button presses. Then, like I said, I have several more sets of these that all go into the same undo stack. Thanks.
The UITextField
#IBOutlet weak var Money: UITextField!
The Undo and Redo Buttons
#IBAction func Undo(_ sender: UIButton)
{
undoManager?.undo()
}
#IBAction func Redo(_ sender: UIButton)
{
undoManager?.redo()
}
The Up One and Down One Buttons
#IBAction func MonDown(_ sender: UIButton)
{
subtractOneMon(Mon: Money)
}
#IBAction func MonUp(_ sender: UIButton)
{
addOneMon(Mon: Money)
}
The Functions Registering the Undo/Redo
func subtractOneMon(Mon: UITextField)
{
undoManager?.registerUndo(withTarget: self, handler:
{(targetSelf) in
targetSelf.addOneMon(Mon: self.Money)
})
let Mon = Double(Money.text!)
let NewMon = Double(Mon! - 1)
Money.text = Int(NewMon).description
}
func addOneMon(Mon: UITextField)
{
undoManager?.registerUndo(withTarget: self, handler:
{(targetSelf) in
targetSelf.subtractOneMon(Mon: self.Money)
})
let Mon = Double(Money.text!) ?? 0
let NewMon = Double(Mon + 1)
Money.text = Int(NewMon).description
}
You might want to add an "Editing Changed" IBAction for your text field.
This function will be called each time any content of the field is changed.
You'd want to store the previous value of the text field somewhere.
Inside the "Editing Changed" IBAction you'd register an undo operation in your undoManager using the previous value and then update the previous value with the current value.
P.S. There's a potential memory leak in the way your handlers are implemented: currently they are holding a strong reference to your UIViewController. Consider adding [weak self] in the capture list. Check out the Memory Management article by John Sundell to learn more.

tvOS PressesBegan called too late (after focus already changes)

My goal is to call doSomething whenever the user presses "up" on their Apple TV remote, only while the topButton is already focused.
The goal is that doSomething should NOT get called if bottomButton was focused. However, when user presses up, the focus moves up to topButton before calling pressesBegan. This causes doSomething to get called regardless of which button was focused.
func doSomething() {
// goal is this function should only run when topButton is focused
}
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard presses.first?.type == UIPress.PressType.upArrow else { return }
print(self.topButton.isFocused)
print(self.bottomButton.isFocused)
if self.topButton.isFocused {
doSomething()
}
}
Note that this workaround doesn't work:
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
guard let previouslyFocusedItem = context.previouslyFocusedItem as? UIButton else { return }
if previouslyFocusedItem == topButton {
self.doSomething()
}
}
Because the focus doesn't update if user presses up while on topButton.
Which leads to more complicated code (would have to add properties and weird-looking if-statements between the two functions that confuse other developers). Is there a more efficient workaround?
Was able to solve it using UIFocusGuide instead. Another issue I found with pressesBegan was that it didn't detect if user swipes up. UIFocusGuide handles both cases.

UICollectionView Cell Button Long Press Gesture return self keep changing?

I'm having something like a shopping cart page, it shows the products the user selected in a collection view, with each cell having 2 button linked to the product that will increase/decrease the amount of product to a singleton cartManager.
So far everything is working, i used protocol to ensure i know which product in the collection view that i'm adding/subtracting the product from. With this code:
protocol CartProductButtonDelegate : class {
func cartProductPlus(_ sender: CartProductCell)
func cartProductMinus(_ sender: CartProductCell)
}
class CartProductCell: UICollectionViewCell{
//labels and image product details etc.
#IBOutlet weak var productMinusBtn: UIButton!
#IBOutlet weak var productPlusBtn: UIButton!
weak var delegate : CartProductButtonDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let tapPlusGesture = UITapGestureRecognizer(target: self, action: #selector(productPlusBtnTapped(_:)))
tapPlusGesture.numberOfTapsRequired = 1
self.productPlusBtn.addGestureRecognizer(tapPlusGesture)
let tapMinusGesture = UITapGestureRecognizer(target: self, action: #selector(productMinusBtnTapped(_:)))
tapMinusGesture.numberOfTapsRequired = 1
self.productMinusBtn.addGestureRecognizer(tapMinusGesture)
}
#objc func productMinusBtnTapped(_ sender: UITapGestureRecognizer) {
delegate?.cartProductMinus(self)
}
#objc func productPlusBtnTapped(_ sender: UITapGestureRecognizer) {
delegate?.cartProductPlus(self)
}
}
And in my UIViewController, i add the collectionview delegate,datasource, and the custom protocol and make all the cell's delegate to the viewcontroller in cellForItem. Everytime i add or subtract the product, i reload the collectionview in order to show the correct amount on the cell label.
func cartProductPlus(_ sender: CartProductCell) {
guard let tappedIndexPath = self.cartCollectionView.indexPath(for: sender) else {
debugPrint("GUARD BROKE GETTING INDEX PATH FOR PRODUCT PLUS TAPPED")
return
}
let product = self.productList[tappedIndexPath.item]
debugPrint("cart Product Plus on product name: \(product.name), index : \(tappedIndexPath.item)")
if let maxBought = Int(product.maxBought ?? ""){
if cartManager.numberOfProductsInCart(product: product) < maxBought{
cartManager.addProduct(product: product)
}
}
self.rearrangeArray()//this is to reload the collection view as well as update UI on cart and someother stuff
}
The problem arise when i tried to add long press gesture using existing logic, for those people who want to buy in bulk.
I've tried to implement this:
let longPressPlusGesture = UILongPressGestureRecognizer(target: self, action: #selector(productPlusLongPressed(_:)))
self.productPlusBtn.addGestureRecognizer(longPressPlusGesture)
#objc func productPlusLongPressed(_ sender: UILongPressGestureRecognizer){
if sender.state == .began || sender.state == .changed{
delegate?.cartProductPlus(self)
}
}
However when i long pressed the button, the item its adding are mixed up, the debug message are showing the collection view cell's index i'm receiving is going up in ascending order 0,1,2,3 then repeat 0,1,2,3 (depending on how many products are there in the collection view cell)
So question, is there a way to fix this? should i not reload collection view when i'm long pressing, if so, how do i update the UI to inform the user. Is there other method to work around the problem or should i just give up the idea of long press and just allow the user to tap the amount and edit it?
Alright, found a work around. After i've implemented the long press delegate, i split the protocol to two extra functions, one for when long press began/still press and the other for when long press ends.
func cartProductLongPlusStarted(_ sender: CartProductCell)
func cartProductLongMinusStarted(_ sender: CartProductCell)
func cartProductLongPlusEnded(_ sender: CartProductCell)
func cartProductLongMinusEnded(_ sender: CartProductCell)
However i do not update the UI from the viewcontroller when long press is active, i just update them from the cell itself. The cell will just hardcode to update the UI and only when the long press is finished, the view controller will just update the UI again.
#objc func productPlusLongPressed(_ sender: UILongPressGestureRecognizer){
if sender.state == .began || sender.state == .changed{
delegate?.cartProductLongPlusStarted(self)
if var amount = Int(self.productCountLabel.text ?? "0"){
if self.maxAmount != nil{
if amount < self.maxAmount!{
amount += 1
}
}else{
amount += 1
}
self.productCountLabel.text = String(amount)
}
}else{
delegate?.cartProductLongPlusEnded(self)
}
}
The only minor issue is that the long press seems to update too fast, the value might be updating abit too fast for the user to react when to stop properly, any idea to slow the updating the long press function call a bit?

windowWillClose and button action not called Swift

I'm designing a mac app with Xcode 10 (beta) and I got an issue with the Preference Window Controller
I have in my Main.storyboard a NSWindowController of custom class PreferenceWindowController with a toolbar. Here are its connections :
Here is the full class :
class PreferenceWindowController: NSWindowController, NSWindowDelegate {
#IBAction func didClickAuthor(_ sender: Any) {
print("author")
}
#IBAction func didClickTypo(_ sender: Any) {
print("typo")
}
override func windowDidLoad() {
super.windowDidLoad()
}
func windowWillClose(_ notification: Notification) {
print("willClose")
}
}
The window is initiated via the AppDelegate class with this code :
let storyboard = NSStoryboard(name: "Main",bundle: nil)
if let wc = storyboard.instantiateController(withIdentifier: "PreferenceWindowController") as? PreferenceWindowController
{
wc.showWindow(self)
}
The window opens as expected, with the toolbar clickable, but no functions from PreferenceWindowController are called at all, neither the closing of the window, nor the clicks on the toolbar.
I checked every connections, every class name, and I really don't know what's wrong...
SOLUTION
The solution is to store the PreferenceViewController class inside the AppDelegate class as a variable.
My solution :
var preferenceWindowController:PreferenceWindowController? = nil
#IBAction func clickPreferences(_ sender: Any) {
if let wc = storyboard.instantiateController(withIdentifier: "PreferencesWindowController") as? PreferenceWindowController {
let window = wc.window
preferenceWindowController = wc
wc.showWindow(self)
}
}
Thank you for helping !
The comment above seems like it could be on the right track. Based on the code context you've included in your question, it looks like the window controller you create will only have a lifetime for that function call.
Try making the window controller an instance variable. This is normally how I wire things up in an App delegate that creates window controllers. It's a simple pattern that works well.

Swift 3 hide NSButton immediately after click

How can I have a button disappear after it's been clicked?
#IBAction func onClick(_ sender: NSButton) {
sender.isHidden = true;
//...a lot of blocking instructions below this line
}
The above works to a certain extent, as the "sender" / button is hidden only after all of the instructions in the function have been processed. I have some blocking IO in the function (socket connections etc.) and I want the button to disappear before all that happens.
I tried using both outlets and sender.
#IBAction func onClick(_ sender: NSButton) {
sender.isHidden = true;
DispatchQueue.main.async {
//...a lot of blocking instructions below this line
}
}
I managed to achieve the desired effect by putting the "blocking" piece of code in the following statement (and pushing the .isHidden setting through immediately, in a synchronous fashion):
DispatchQueue.main.async { /*code*/ }