How to prevent VoiceOver allowing interaction with hidden UIDatePicker - ios

I have implemented an in-line UIDatePicker in a UITableView just like they appear in other iOS apps, for example when creating a new event in Calendar.
While using the app with VoiceOver, the date picker is still accessible after the user selected a date and then closed the date picker by double tapping when on the table view cell. When the date picker hides, VoiceOver highlights the appropriate cell, but if the user then swipes to the right it will select the now hidden UIDatePicker and allow them to interact with it. I expected it to select the next table view cell like it does in Calendar.
I have read on other SO questions that hidden views are still accessible, and in order to prevent that from occurring, you can set the property accessibilityElementsHidden to YES on the container view after you hide the element to let VoiceOver know it's no longer on screen. But this did not work for me. I also read you should post a layout change notification so VoiceOver knows to update to the current UI, but that too didn't work and the hidden view is still accessible.
How can I prevent my hidden UIDatePicker from being accessible? Note that when the table loads, the hidden date picker is not accessible. So only after it appears then hides is it still accessible.
This is my code to hide the date picker when the user taps the cell:
self.datePicker.hidden = YES;
self.datePicker.alpha = 0.0f;
self.datePickerCell.accessibilityElementsHidden = YES;
self.datePickerCell.contentView.accessibilityElementsHidden = YES;
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
And in heightForRowAtIndexPath the datePickerCell height is changed to 0 upon hide. Note that the datePicker is a subview of the datePickerCell.

I tried to replicate your scenario on my own, and came up with something that works well with accessibility. The most important decision in my opinion was to use an NSLayoutConstraint for the height of the Date Picker.
This is an overview of the Storyboard I designed:
Static Tableview Storyboard
And this is what I did in the ViewController:
#IBOutlet weak var pickerHeightConstraint: NSLayoutConstraint!
var showPicker = false
#IBOutlet weak var datePickerView: UIDatePicker!
override func viewDidLoad() {
super.viewDidLoad()
pickerHeightConstraint.constant = 0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
showPicker = !showPicker
datePickerView.alpha = showPicker ? 1.0 : 0.0
pickerHeightConstraint.constant = showPicker ? 224 : 0
tableView.beginUpdates()
tableView.endUpdates()
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 1 {
return pickerHeightConstraint.constant
}
return UITableViewAutomaticDimension
}
Note that I didn't had to mess around with UIAccessibility properties such as accessibilityElementsHidden. Just use the constant property of the Layout Constraint to manipulate the date picker view element.
You can find a project where I tested this here. A Gif can be found in the README so you can quickly see a short demo - my reputation is not enough for more links.

self.datePicker.isAccessibilityElement = FALSE;
That should hide it from Accessibility.

remove the datePicker or use another cell without datepicker

I would suggest you check a variable as accessible, so:
#property(nonatomic,assign) BOOL isAccessible;
and in viewDidLoad set property to accessible = NO;
self.isAccessible = NO;
and in UITableViewDelegate :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if(self.isAccessible)
{
//Do your date picker functionality
}
//For else u dont need cuz it will do no function if isAccessible is set to NO;
}

Related

Not able to change view colour in UICollection view

I am trying to change colour of the view inside the uicollectionviewcell. But I am not able to change the colour. When I try to connect to the UIViewController to our view controller it say " cannot connect the repetitive content as an outlet.".
When I change the background of the cell it comes like this
As to make it round I am using view and the giving it layer radius properties.
What I am trying to achieve is:
The values are coming from the model class that I have created and assigned it to UIcolectionviewcell. Model contains only one text field that shows the tags.
When user select any tags the background and text colour will change.I am not to achieve this. It might be easy to do but somehow I am not able to achieve this.
Try to change the background color of your rounded element and not of the entire cell
You can create custom UICollectionViewCell and use it to access different items inside of it, like the textfield with your tag
I've added the sample code to achieve your requirements, Please refer and try implementing based on the following code:
//Your model class
class TagModel{
var tag:String!
var selected:Bool!
init(tag:String, selected:Bool) {
self.tag = tag
self.selected = selected
}
}
//your cell with Xib
class TagCell:UICollectionViewCell{
#IBOutlet weak var tagLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
func setTag(_ tagModel:TagModel){
tagLabel.layer.masksToBounds = true
tagLabel.layer.cornerRadius = tagLabel.frame.size.height/2
tagLabel.text = tagModel.tag
if tagModel.selected{
tagLabel.textColor = .white
tagLabel.backgroundColor = .blue
}else{
tagLabel.textColor = .gray
tagLabel.backgroundColor = .lightGray
}
}
}
//Your ViewController which has `UICollectionView`
class TagViewController:UIViewController, UICollectionViewDelegate, UICollectionViewDataSource{
var tagModels:[TagModel]!
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tagModels[indexPath.item].selected = !tagModels[indexPath.item].selected
collectionView.reloadItems(at: [indexPath])
}
}
Note: Please take this code as sample and make modifications based on your implementations.

buttons and labels resetting when scrolling through collection view

I have a collection view in which each cell possess the ability to be interacted with by the user. Each cell has a like button and a number of likes label. When the button is pressed, the button should turn cyan, and the label (which holds the number of likes) should increment. This setup currently works. However, when I scroll through the collection view and scroll back, the button reverts to its original color (white) and the label decrements down to its original value. I have heard of an ostensibly helpful method called prepareForReuse(), but perhaps I'm not using it correctly. Here is my code:
Here is the array which holds all the cells
var objects = [LikableObject]()
Here is the class definition for these objects
class LikableObject {
var numOfLikes: Int?
var isLikedByUser: Bool?
init(numOfLikes: Int, isLikedByUser: Bool) {
self.numOfLikes = numOfLikes
self.isLikedByUser = isLikedByUser
}
}
Mind you, there is more functionality present in this object, but they are irrelevant for the purposes of this question. One important thing to be noted is that the data for each cell are grabbed using an API. I'm using Alamofire to make requests to an API that will bring back the information for the numOfLikes and isLikedByUser properties for each cell.
Here is how I load up each cell using the collection view's delegate method:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ObjectCell", for: indexPath) as! ObjectCell
cell.configureCell(
isLikedByUser: objects[indexPath.row].isLikedByUser!,
numOfLikes: objects[indexPath.row].numOfLikes!,
)
return cell
}
The ObjectCell class has these three fields:
var isLikedByUser: Bool?
#IBOutlet weak var numOfLikes: UILabel!
#IBOutlet weak var likeBtn: UIButton!
And that configureCell() method, which belongs to the cell class, is here:
public func configureCell(numOfLikes: Int, isLikedByUser: Bool) {
self.isLikedByUser = isLikedByUser
self.numOfLikes.text = String(numOfLikes)
if isLikedByUser {
self.likeBtn.setFATitleColor(color: UIColor.cyan, forState: .normal)
} else {
self.likeBtn.setFATitleColor(color: UIColor.white, forState: .normal)
}
}
And lastly, the prepareForReuse() method is here:
override func prepareForReuse() {
if isLikedByUser! {
self.likeBtn.setTitleColor(UIColor.cyan, for: .normal)
} else {
self.likeBtn.setTitleColor(UIColor.white, for: .normal)
}
}
This doesn't work. And even if it did, I still don't know a way to keep the numOfLikes label from decrementing, or if it should anyway. I'm speculating that a big part of this problem is that I'm not using the prepareForReuse() method correctly... Any help is appreciated, thank you.
prepareForReuse is not the place to modify the cell, as the name states, you "only" have to prepare it for reuse. if you changed something (for example isHidden property of a view), you just have to change them back to initial state.
What you should do though, you can implement didSet for isLikedByUser inside the cell, and apply your modifications to likeBtn in there. (this is of-course the fast solution)
Long solution: It's an anti-pattern that your cell has a property named isLikedByUser, TableViewCell is a View and in all architectures, Views should be as dumb as they can about business logic. the right way is to apply these modifications in configure-cell method which is implemented in ViewController.
If you feel you'll reuse this cell in different viewControllers a lot, at least defined it by a protocol and talk to your cell through that protocol. This way you'll have a more reusable and maintainable code.
Currently all of this is good , the only missing part is cell reusing , you have to reflect the changes in the number of likes to your model array
class ObjectCell:UICollectionViewCell {
var myObject:LikableObject!
}
In cellForRowAt
cell.myObject = objects[indexPath.row]
Now inside cell custom class you have the object reflect any change to it , sure you can use delegate / callback or any observation technique
The prepareForResuse isn't needed here.
You do need to update the model underlying the tableview. One way to verify this is with mock data that is pre-liked and see if that data displays properly.

Swift animate view in tableviewcell

For an app that I'm developing, I'm using a tableview. My tableview, has of course a tableviewcell. In that tableviewcell I'm adding a view programmatically (so nothing in storyboard). It's a view where I draw some lines and text and that becomes a messagebubble. If the messagebubble is seen by the other user you sent it too , a line of the bubble will go open.
So I have the animation function inside the class of that UIView (sendbubble.swift)
Now, it already checks if it is read or not and it opens the right bubble. But normally it should animate (the line that goes open should rotate) in 0.6 seconds. But it animates instantly. So my question is, how do I animate it with a duration?
I would also prefer to still call it in my custom UIView class (sendbubble.swift) . Maybe I need code in my function to check if the cell is presented on my iphone?
Thanks in advance!
func openMessage() {
UIView.animate(withDuration: 0.6, delay: 0.0, options: [], animations: {
var t = CATransform3DIdentity;
t = CATransform3DMakeRotation(CGFloat(3 * Float.pi / 4), 0, 0, 1)
self.moveableLineLayer.transform = t;
}, completion:{(finished:Bool) in })
}
First you need to grab the cell.
Get the indexPath of that cell where you need to show open bubble.
Get the cell from tableView.cellForRowAt(at:indexPath)
We will now have access to that bubble view now you can animate using the same function func openMessage()
Any question? comment.
You need to implement the UITableViewDelegate method tableView(_:willDisplay:forRowAt:) https://developer.apple.com/reference/uikit/uitableviewdelegate/1614883-tableview
That is triggered when the cell is about to be drawn - not when it is created. You will also need to store a state in your model that says if the animation has occurred, otherwise it will happen every time the cell comes back into view.
EXAMPLE
In the view controller (pseudo code)
class CustomViewController: UITableViewDelegate {
//where ever you define your tableview
var tableView:UITableView
tableView.delegate = self
var dataSource //some array that is defining your cells - each object has a property call hasAnimated
func tableView(_ tableView: UITableView, willDisplay cell: ITableViewCell,
forRowAt indexPath: IndexPath) {
if let cell = cell as? CustomTableViewCell, dataSource[indexPath.row].hasAnimated == false {
dataSource[indexPath.row].hasAnimated = true
cell.openMessage()
}
}
}
class CustomTableViewCell: UITableViewCell {
func openMessage() { //your method
}
}

tvos UICollectionView lose focus to previous Focus Cell

I am building a tvos application. i have a strange bug where UICollectionView lose focus of the previously selected cell when i navigate back to that particular view. The scenario is some thing this like this.
I have two UIViewControllers A and B. A has a UITableView and it has three prototype cells in it. Each cell has a horizontal scrolling UICollectionView inside it. When i click on any of UICollectionViewCell it navigates to the B (detail page). I am presenting B modally.
Now when i press Menu button on Siri remote view A appears again (in other words view B is removed from View hierarchy) but the current selected cell is different then the previously selected. I have tried to use remembersLastFocusedIndexPath with both true and false values and also tried by implementing
func indexPathForPreferredFocusedViewInCollectionView(collectionView: UICollectionView) -> NSIndexPath?
but the control neves comes to this function when i navigate back to view A. I am also reloading every thing in viewWillAppear function.
Can any one help me in this. Thanks
The property remembersLastFocusedIndexPath should be set to true for the collectionView and false for the tableView.
Also, Are you reloading the UITableView in viewWillAppear i.e Is the table data being refreshed when the B is popped and A appears?
If Yes, then lastFocusedIndexPath will be nil on reload.
We faced the same issue. We solved it by not reloading the contents when B is popped.
Maintain a flag say didPush. Set this flag to true when B is pushed. When A appears check whether the flag is set and only then fetch data and reload table.
This worked for us.
I don't remember exactly, but I know there was a known issue for remembersLastFocusedIndexPath where it wasn't working as intended.
This is one workaround, although take it with a grand of salt as it does seem slightly hacky and it uses the common (but potentially unstable) approach of overriding the preferredFocusedView property.
private var viewToFocus: UIView?
override var preferredFocusView: UIView? {
get {
return self.viewToFocus
}
}
Save locally the indexPath of the last cell in View A when presenting View B
// [1] Saving and scrolling to the correct indexPath:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
...
collectionView.scrollToItemAtIndexPath(indexPath:, atScrollPosition:, animated:)
}
// [2] Create a dispatchTime using GCD before setting the cell at indexPath as the preferredFocusView:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
...
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(CGFloat.min * CGFloat(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.viewToFocus = collectionView.cellForItemAtIndexPath(indexPath:)
// [3] Request an update
self.setNeedsFocusUpdate()
// [4] Force a focus update
self.updateFocusIfNeeded()
}
}
The reason we split the two methods into both viewWillAppear and viewDidAppear is that it eliminates a bit of the animation jump. If anyone else could jump in with suggestions to improve or even alternate solutions, I'd also be interested!
In Swift
If you want to focus collection view Cell then
You can use collectionview Delegate method, method name is
func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator)
{
}
You can use this method like this...
func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if let previousIndexPath = context.previouslyFocusedIndexPath,
let cell = collectionView.cellForItemAtIndexPath(previousIndexPath) {
cell.contentView.layer.borderWidth = 0.0
cell.contentView.layer.shadowRadius = 0.0
cell.contentView.layer.shadowOpacity = 0
}
if let indexPath = context.nextFocusedIndexPath,
let cell = collectionView.cellForItemAtIndexPath(indexPath) {
cell.contentView.layer.borderWidth = 8.0
cell.contentView.layer.borderColor = UIColor.blackColor().CGColor
cell.contentView.layer.shadowColor = UIColor.blackColor().CGColor
cell.contentView.layer.shadowRadius = 10.0
cell.contentView.layer.shadowOpacity = 0.9
cell.contentView.layer.shadowOffset = CGSize(width: 0, height: 0)
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: [.CenteredHorizontally, .CenteredVertically], animated: true)
}
}

uitableviewcell with textfield pass changed data

i have a custom uitableviewcell with a label and a textfield now i want to pass the data from the textfield when it is changed to the uitableviewcontroller.
The tableview is populated with data from a sqlite database that is wrapped with fmdb and the number of tableviewcells is different in every db i use in the app. How many fields there are and what name they have is stored in the db, the population of the table works fine.
I managed to get a "EditingDidEnd" event from the textfield in the cells to my view controller, the event is used to activate a toolbar button for saving changes. I did this via the cell.fieldData.addTarget(self, action: "SaveButtonOn:", forControlEvents: UIControlEvents.EditingDidEnd)
on creating the Cells in
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath).
My problem is now that i need to get the data from the textfields inside the cells. I have found some code and tutorials for passing parameters to the via addTarget() but only for buttons and i don't know if that is usable for textfields.
If you need more information just ask, i hope you can decipher what i need to do.
Thanks for any answers
Adarkas
Use delegation with your UITextField:
import UIKit
class MyViewController: UIViewController, UITextFieldDelegate {
var myTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextField = UITextField(frame: CGRectMake(0, 0, 100, 30))
myTextField.delegate = self
}
func textFieldDidEndEditing(textField: UITextField) {
// Do stuff here.
}
}
If properly set, any textField you have calls the delegate-method textFieldDidEndEditing and from there on you can do with its content (textField.text) what you want.
To get value from a UITextFeild in a specific cell of UITableView:-
Make sure you set a tag for your UITextFeild first, it's better you set tag with indexpath.row like cell.textFld.tag=indexpath.row
Now write the following code to retrieve value from textfeild:-
let indexPath = NSIndexPath(forRow:row, inSection:0) //just provide the row number from where you want to fetch the textfeild
let cell : UITableViewCell? = self.tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell?
(cell.contentView.viewWithTag(2) as UITextFeild).text // access the value of textfeild by its tag with this way

Resources