UIAccessibility announcing n of n elements on custom views - ios

When you add a UISegmentedControl to a view, UIAccessibility will focus on it and say:
"(Selected) ItemName Button 1 of 2"
"ItemName Button 2 of 2"
I have a custom control that has UIButtons that toggle similar to a UISegmentedControl. But what I'm trying to figure out is how to get the Voice Over to announce the n of n at the end.
The closest thing that I've found is assigning the .accessibilityTraits = .tabBar on the container. The issue is that it announces:
"ItemName Button Tab 2 of 2"
But to conform to our accessibility guidelines we can't have it announce "tab".
https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits/1648592-tabbar
Short of just writing a custom accessibilityLabel is there anything within UIAccessibility that can handle this logic?

Set the container view's accessibilityTraits = .tabBar
Set the container view's accessibilityElements = [button1, button2]
Set each button's accessibilityTraits = .selected or .none when necessary

I have a custom control that has UIButtons that toggle similar to a UISegmentedControl. But what I'm trying to figure out is how to get the Voice Over to announce the n of n at the end.
Put each one of your UIButton elements in the accessibilityElements array of the custom control that acts like a container.
By searching a specific element in this array, you will have an index 'x' inside a total amount 'N' of buttons: "item name button x of N".
In your UIButton, set accessibilityLabel by inserting the result of the previous research.
Here's a kind of logic that should help you reach your purpose with a little bit of code as follows for instance (Xcode 10.2.1, Swift 5.0, iOS 12):
class ButtonsViewController: UIViewController {
#IBOutlet weak var myCustomContainer: UIView!
#IBOutlet weak var btn1: UIButton!
#IBOutlet weak var btn2: UIButton!
#IBOutlet weak var btn3: UIButton!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
myCustomContainer.accessibilityElements = [btn1!, btn2!, btn3!]
let nbButtons = myCustomContainer.accessibilityElements?.count
for (index, elt) in (myCustomContainer.accessibilityElements?.enumerated())! {
let btn = elt as! UIButton
let btnName = btn.titleLabel?.text
btn.accessibilityLabel = btnName! + String(index + 1) + " of " + String(describing: nbButtons!)
}
}
}

Related

How to programatically add floating header bar to a UIScrollView

Okay, someone please shed some light here. I am very new to coding, still. Ive been learning swift for about a month or so and have taken the route of designing my first super simple app as my learning path. This gives me purpose that other app builds lacked but is a different learning curve in and of itself.
In this situation I have created almost my entire UI in Xcode Interface Builder using auto-layout and graphics. This creates the situation where there is no code at all for the UI. Great and simple to learn fast however, now I need to do something programmatically (so it seems) and I don't even know where to start.
I have a scrollview, it puts out about 100 lines of calculations, in 9 columns. The view is larger and scrolling vertical and horizontal. I also have a label header describing each column. When the user scrolls down (data up) it is impossible to keep track of which column is which because the label header disappears. I am trying to figure out how to add a floating header to my scrollview just like you would "freeze" a header in spreadsheets.
Thus far I've tried everything I could in Interface Builder, all the searches I've done turn up only programmatic solutions, which is fine except that I have no UI code to attach to...
So I really have one question:
How do you add a floating header in UI Builder OR programmatically when there is no initial code? I want my labels horizontal stackview to sit still as a floating header.
Any help is much appreciated, been racking my head over this the last 3 whole days and its the last change before I'm done my app :(
Edit: here is my code and a few screenshots of initial view, scroll right and scroll down. As you can see there is no UI code really. I want to do something like this but not with a tableView: https://mariusschulz.com/blog/adding-a-fixed-header-to-a-uiscrollview
Even more ideally something like this but not with a table or collectionView... https://imgur.com/a/vwLzy2v
import UIKit
class MetricVerticalViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var shelfHeight: UITextField!
#IBOutlet weak var squeezeFour: UILabel!
#IBOutlet weak var squeezeThree: UILabel!
#IBOutlet weak var squeezeTwo: UILabel!
#IBOutlet weak var squeezeOne: UILabel!
#IBOutlet weak var normalGauge: UILabel!
#IBOutlet weak var gainOne: UILabel!
#IBOutlet weak var gainTwo: UILabel!
#IBOutlet weak var gainThree: UILabel!
#IBOutlet weak var gainFour: UILabel!
override func viewDidLoad() {
shelfHeight.delegate = self
super.viewDidLoad()
}
// Calculate button tapped will cause math to occur and the text field to show the values
#IBAction func calculateSpacing(_ sender: UIButton) {
squeezeFour.text = ""
squeezeThree.text = ""
squeezeTwo.text = ""
squeezeOne.text = ""
normalGauge.text = ""
gainOne.text = ""
gainTwo.text = ""
gainThree.text = ""
gainFour.text = ""
// Resigns the keyboard when the button is pressed
self.view.endEditing(true)
calculateSpacing()
}
// Acts to dismiss number keyboard when user taps outside
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
brickHeight.resignFirstResponder()
}
func calculateSpacing() {
let shelfHeight = Int(shelfHeight.text!) ?? 1
let squeezeFourGauge = shelfHeight + 6
let squeezeThreeGauge = shelfHeight + 7
let squeezeTwoGauge = shelfHeight + 8
let squeezeOneGauge = shelfHeight + 9
let normalJoint = shelfHeight + 10
let gainOneGauge = shelfHeight + 11
let gainTwoGauge = shelfHeight + 12
let gainThreeGauge = shelfHeight + 13
let gainFourGauge = shelfHeight + 14
for index in 1...100 {
squeezeFour.text! += "Shelf \(index) = \(index * squeezeFourGauge)\n"
squeezeThree.text! += "Shelf \(index) = \(index * squeezeThreeGauge)\n"
squeezeTwo.text! += "Shelf \(index) = \(index * squeezeTwoGauge)\n"
squeezeOne.text! += "Shelf \(index) = \(index * squeezeOneGauge)\n"
normalGauge.text! += "Shelf \(index) = \(index * normalJoint)\n"
gainOne.text! += "Shelf \(index) = \(index * gainOneGauge)\n"
gainTwo.text! += "Shelf \(index) = \(index * gainTwoGauge)\n"
gainThree.text! += "Shelf \(index) = \(index * gainThreeGauge)\n"
gainFour.text! += "Shelf \(index) = \(index * gainFourGauge)\n"
}
}
extension ViewController : UITextFieldDelegate {
// Acts to dismiss keyboard once user presses return (possibly unnecessary as this was used for text keyboard input in original code)
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
One way to access Interface Builder items from code is via an outlet connection. You have to open your code next to Interface Builder, then control-drag the item into your view controller code and make sure to choose "Outlet" as the connection type. See Apple's tutorial for more info.
So I did a little work and was actually able to create a floating header of the sizes in the above screenshots by adding a second view to the scrollview and constraining it like crazy. It worked great in the iPhone 11 Pro Max size because it was constrained using that size but then every other device was screwed up. I will try and set "vary for traits" constraints for other sizes and see if its a stable workaround.
If it works again i'll post some kind of write-up.
Apparently its bad to have more than one view inside a scrollView... worked okay for me.
Anyone else have any better solutions to this?

How to change button programmatically without outlet connection in swift

I have a list of buttons inside an array. I want to replace the buttons position order using that array. I can do this in java, but cant do the same in swift. Here is my code:
#IBOutlet weak var menuView: UIView!
struct ButtonsIndex {
var button: UIButton
var index: Int
}
var footer: [ButtonsIndex] = []
//Set Buttons order etc
...
func setFooter(){
let count = 0
for buttons in menuView.subviews {
if var button = buttons as? UIButton {
button = footer[count].button
count += 1
}
}
}
Is it impossible to change the buttons position without connecting the outlets to my code? Thanks in advance.

Sum of three textfield value without clicking a button (Swift - Xcode)

I'm currently having problems for my label to read the addition of 3 textfield values automatically, without a button function action. As such i only want my textfield to be an Int input only. There's a screenshot attached below for better reference. Appreciate those who can help me with this. Thanks!
ViewController
import UIKit
class TryingoutController: UIViewController {
#IBOutlet var impact: UITextField!
#IBOutlet var rigour: UITextField!
#IBOutlet var response: UITextField!
#IBOutlet var total: UILabel!
One way is to add self as the target to the text fields, for the control event .editingChanged
impact.addTarget(self, action: #selector(textChanged), for: .editingChanged)
// do the same for other textfields
Then declare a textChanged method. This should handle what happens when the texts in the text fields change. One implementation would be to add up all the values in the text fields (if any, and is valid) and display it in the label.
func textChanged() {
let impactValue = Int(impact.text!)
let rigourValue = Int(rigour.text!)
let responseValue = Int(response.text!)
total.text = String(describing:
(impactValue ?? 0) + (rigourValue ?? 0) + (responseValue ?? 0)
)
}
Optionally, you can conform to UITextFieldDelegate:
class TryingoutController: UIViewController, UITextFieldDelegate {
}
and implement shouldChange according to this answer by Thuggish Nuggets. Then, set the delegates of the text fields to self:
impact.delegate = self
// do the same for other text fields.

How do I change the background color of a button within a custom UITableViewCell programatically in Swift?

I have a custom UITableViewCell and one of its subviews is a button (with the title "RSVP"):
It is connected to the following code:
class SelectedEventsTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var attendanceLabel: UILabel!
#IBOutlet weak var attendanceButton: UIButton!
}
I am trying to change the background color of the button by adding this code to the SelectedEventsTableViewCell class:
override func awakeFromNib() {
super.awakeFromNib()
attendanceButton.backgroundColor = UIColor.greenColor()
}
However, it doesn't work. The button gets a white background:
Does anybody know why this is happening? It also doesn't work when I set the button's background color programmatically when the button is pressed.
Your help is appreciated.
Check somewhere you set the button background color white.
attendanceButton.backgroundColor = UIColor.whiteColor()
Check where you use SelectedEventsTableViewCell's object in TableView's data source or delegate methods in viewController.
ex.
cell.attendanceButton.backgroundColor = UIColor.whiteColor()
There are several possible reasons.
1.Did you make that outlet accessible? If not, CTRL-DRAG from storyboard.
2.Did you change that after awakeFromNib()? Maybe you set it again in cellForIndexPath(table view data source).
3.Cell was reused but awake once. It means you should recover its state after reuse. prepareForReuse() is designed for this.
I know this issue is old, but I ran into the same thing on iOS 15. The solution is to use the new UIButton.Configuration struct.
if #available(iOS 15.0, *) {
var config = UIButton.Configuration.filled()
config.baseBackgroundColor = UIColor.greenColor()
attendanceButton.configuration = config
} else {
attendanceButton.backgroundColor = UIColor.greenColor()
}
Run something like that in your prepareForReuse function or after you dequeue the cell and it should start working again. There are a lot more customisation options in iOS 15 using that configuration struct so have a dig into it for configuring all other aspects of your buttons.

bindTo rx_text of a UITextField did not trigger the UITextField's emit event

I'm a new one to learn RxSwift.
I modified the Simple Numbers example in the RxSwift Example App, which will add three numbers into a result number.
I add a testStr UITextField, and an upperCase UILabel. I map testStr to uppercase and bindTo the upperCase label, that's good. And I also map testStr to its length, and bindTo the num1 field. Strange things happen, although the contents of the num1 field changes, it does not emit any event, so it has no effect on the result label. Even if I input some number into another number field, the result number does not count the num1.
Have I made any wrong use of bindTo? In what way can I make the num1 emit an event?
Thanks!!!
#IBOutlet weak var num1: UITextField!
#IBOutlet weak var num2: UITextField!
#IBOutlet weak var num3: UITextField!
#IBOutlet weak var result: UILabel!
#IBOutlet weak var testStr: UITextField!
#IBOutlet weak var upperCase: UILabel!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Observable.combineLatest(num1.rx_text, num2.rx_text, num3.rx_text) {
(textval1, textval2, textval3) -> Int in
return (Int(textval1) ?? 0) + (Int(textval2) ?? 0) + (Int(textval3) ?? 0)
}
.map{$0.description}
.bindTo(result.rx_text)
.addDisposableTo(disposeBag)
let obStr = testStr.rx_text
obStr
.map {$0.uppercaseString}
.bindTo(upperCase.rx_text)
.addDisposableTo(disposeBag)
obStr
.map{ $0.characters.count }
.map{ $0.description }
.bindTo(num1.rx_text)
.addDisposableTo(disposeBag)
}
You need to use a Subject such as Variable to store the value. You can see an example of this in this answer in the section called Using Variables.
The reason it doesn't work is because rx_text will only omit a next element when it's changed by the user, not programmatically (as you're doing). This is because rx_text is really using this method from UIControl to get notified of changes to the field:
public class UIControl : UIView {
public func addTarget(target: AnyObject?, action: Selector, forControlEvents controlEvents: UIControlEvents)
}
However, that method does not call the action method on target when the change happens programmatically. Only when it happens due to a user changing something.
So, you should see a next event if you were to change the field programmatically and then the user were to tap into (or out of) the field. However, that's not what you want.
Instead, refer to that answer I linked you to and it will work.

Resources