How to make a segment controller using AsyncDisplayKit in swift 3 - ios

I have two ASTableNode
1. Notifications tabelNode
2.Comments tabelNode
now I want to make a segment controller as a sticky header on the table, when I click on Notifications segment the notificationTable should appear and when I click on comments segment the commentsTable should appear as in this image WatchList and Following Segment,How can I achieve this any help appreciated.

A bit late to the party, but hope this helps. It's not difficult at all to use native views for displaying nodes.
Here is how it's done for the segment control:
///Keep a reference to the segment control
private var segmentedView: UISegmentedControl?
///This node will contain the segment control
private lazy var segmentedNode: ASDisplayNode = {
///The node is initialized with a view block that initializes the segment
return ASDisplayNode(viewBlock: { () -> UIView in
self.segmentedView = UISegmentedControl(items: ["Watchlist", "Following"])
///Select Watchlist maybe? Your call.
self.segmentedView.selectedSegmentIndex = 1
///Configure additional appearance of the segment control
return self.segmentedView
})
}()
After that, do any node operations you want (i.e. include in stack view, set style) on the node, and any segmented control operations you want (i.e. value changed selector) on the native control.

Related

Show or hide items by clicking button

I have four imageview contents in an XIB and a button that covers all my XIB. I want to make when the user tap the button, the first imageview is shown, the next tap is hidden and the second imageview is displayed and so on until all my imageview is shown / hidden. What would be the most efficient way to do it?
Save all your UIImageViews to an array, and current showing imageView to a variable, it may look like this:
var imageViews: [UIImageView] = []
var currentImageViewIndex = 0 {
didSet {
if currentImageViewIndex >= imageViews.count { currentImageViewIndex = 0 }
imageViews[oldValue].isHidden = true
imageViews[currentImageViewIndex].isHidden = false
}
}
func handleTap() {
currentImageViewIndex += 1
}
I suggest you use a state variable that contains an enum listing the various states (firstImageVisible, secondImage.... ) then you can have a function inside the enum that switches to the nextState (being the target of your button action) you can also easily iterate through states of an enum, check the documentation for the CaseIterable protocol. Often having a property observer (didSet) on the state is a handy place to update other parts of the UI which need to change every time the state changes.

Handle the button list in UITableViewCell

I’m continuing to the last ViewController in my movie booking project. Because I’m new on iOS so please apologize if my question is so popular and very basic. Thank you in advance!.
So I have trouble to solve problem with the list of buttons in the cell.
E.g:
I have the swift class file for the button:
class ButtonReverse: UIButton {
require.init()
//something here. Color, etc.
}
class TypeOfSeat {
var normalSeat: Bool
var coupleSeat: Bool
var reversedSeat: Bool
var reversingSeat: Bool
// something here
}
class Total: TypeOfSeat {
var priceOfSeat: Int
func invoice () -> Int {
/// something here to return total of invoice.
// return total }
In the UITableViewCell.
I have list of the buttons to be ordered vertically.
How can I loop over the cell and create list of the buttons in one cell and random the button color that I have defined before be implement the function:
cellForRows at: IndexPath
All of buttons will be sorted in just one cell. Red is reversed seats. Yellow is reversing seat. And Green is available seat ready for reverse.
I have idea to use uicollectionview. But I want to use it with tableview only because i’m just starting with uitableview.
To handle the button in the cell I’m just using simple function to call delegate for UIAlert and show message: the seat is reversed or show the total......when user touch the button.
Many thanks,
To achieve your desired result you can follow these steps :
Use CollectionView.
Take your colors in an array and use random to get color randomly and make an array for your collection (say dataArray).
If you are using buttons just to click, then don't use them instead you can give cell's content view (or you can take one view in cell) corner radius to make them circle and assign the color.
Then implement didSelectItem method to get the click item of particular cell (or color) and fetch the data from your dataArray based on indexPath.item as index.

iOS - How to use a small view in different view controllers in Swift

I have a progress bar (with its own controller). This bar is supposed to be shown in different views depending on which view is visible. As the progress will be same, If possible I don't want to create many progress bar in many views rather I want to use same instance in all these views. Also in that way when I need to change any property of the progress bar it will be reflected commonly, which is required.
Please suggest me how can I use this common view. And also if my strategy is wrong, what would be the better design for such scenarios.
1) Well you have 2 options. You can declare a new Class ViewBox (or whatever name) and then use that inside your code
First View Controller
var box:ViewBox = ViewBox()
When you segue or transition to your next screen, you can have a predefined variable var box:ViewBox!. Then say when you press a button, the button has a function called transition.
//Now setup the transition inside the Storyboard and name the identifier "toThirdViewController"
override func prepareForSegue(segue:UIStoryboardSegue, sender:AnyObject?) {
if(segue.identifier == "toThirdViewController") {
var vc = segue.destinationViewController as! `nextViewController` //The class of your next viewcontroller goes here
vc.box = self.box
}
//Since The SecondViewController doesn't need ViewBox, we don't need it there.
}
where
nextViewController:UIViewController {
var box:ViewBox!
}
Or you could do a much simpler way and that is to look up a UIPageViewController :)

iOS UI Automation element finds no sub-elements

I'm just starting out with UI Automation for my iOS app and am already having trouble. I'm unable to attach screen shots so I'll do my best to describe my scenario.
I'm building for iOS 6.0 and am using a storyboard. The app launches to a screen with a navigation controller. The root view controller contains a main view that has 1 UIView subview that takes up the bottom 60% of the screen and a segmented control that sits above that subview. I was able to configure accessibility for the main view (label "mainview"). I am then able to locate this element in my test no problem. However, I am now having trouble finding the segmented controller. So I decided to log out the length of "elements()" and "segementedControls()" from my "mainview" element and the length of each array is 0. So somehow when the test is running my app it's saying there are no sub-elements on my main view.
Another thing to note is that I could not find any accessibility section in the identity inspector of the storyboard editor for the segmented control. However I temporarily added a button to my main view and configured that with an accessibility label, just to test if the elements() or buttons() calls would subsequently show an element for the main view when running my test, but these arrays were still returning as empty, even with the button.
Here's my script:
var target = UIATarget.localTarget();
var app = target.frontMostApp();
function selectListView() {
var testName = "selectListView";
UIALogger.logStart(testName);
var view = app.mainWindow().elements()["mainview"];
if (!view.isValid()) {
UIALogger.logFail("Could not locate main view");
}
UIALogger.logMessage("Number of elements for sub element: " + view.elements().length);
var segmentedControl = view.segmentedControls()[0];
if (!segmentedControl.isValid()) {
UIALogger.logFail("Could not locate segmented control on physician collection parent view");
}
var listButton = segmentedControl.buttons()[1];
if (!listButton.isValid()) {
UIALogger.logFail("Could not locate list button on segemented controller on physician collection parent view");
}
UIALogger.logMessage("Tapping List button on physician collection view's segmented control");
listButton.tap();
UIALogger.logPass(testName);
}
selectListView();
Any help greatly appreciated.
EDIT: I added this to my script to search the entire view hierarchy from the main window, set an accessibility label value for my segmented control in initWithCoder (since I don't seem able to set one in the storyboard editor for a segmented control, as I stated earlier) and still could not find the element - it's as though it's just not in the view hierarchy, though it's on the screen and functions just fine:
function traverse(root, name) {
if (root.name() == name) {
return root;
}
var elements = root.elements();
for (var i = 0; i < elements.length; i++) {
var e = elements[i];
var result = traverse(e, name);
if(result != null) {
return result;
}
}
return null;
}
function selectListView() {
var testName = "selectListView";
var segmentedControl = traverse(UIATarget.localTarget().frontMostApp().mainWindow(), "mysegementedcontrol");
if (segmentedControl == null) {
UIALogger.logMessage("Still could not find it");
}
....
}
EDIT: Added call to app.logElementTree() and still no segmented control in sight ("PhysicianCollectionParentView" is my "mainview" - you can see, no sub-elements there):
EDIT: Here are some screen shots. The first shows my "master" view controller. The next shows that in addition to the segmented control there is also a UIView subview. The 3rd shows the basic entry point for the app in my storyboard.
Here is the class extension for my "master" view controller here, showing the outlets for the segmented control and the other UIView subview:
#interface PhysicianCollectionMasterViewController ()
#property (strong, nonatomic) IBOutlet UISegmentedControl *viewSelectionControl;
#property (strong, nonatomic) IBOutlet UIView *physicianCollectionView;
#end
EDIT: Here's something very interesting - I decided to go with a brand new script created within instruments and take advantage of the record feature. When I clicked on my segmented control, here's the JavaScript it created to show me how it had accessed one of the buttons on my segmented control:
var target = UIATarget.localTarget();
target.frontMostApp().mainWindow().elements()["PhysicianCollectionParentView"].tapWithOptions({tapOffset:{x:0.45, y:0.04}});
So, I guess worst-case I could go with something like this, but it just makes no sense to me that UI Automation just does not think that the control exists. So strange. There must be something I'm missing but my setup is so basic I can't imagine what it could be.
When you set accessibilityLabel for an element and flag it isAccessibilityElement = YES; the subviews of that element are hidden. For automation, you should use accessibilityIdentifier instead of accessibilityLabel and set isAccessibilityElement = NO;
In your objective C code after physicianCollectionView is rendered, remove the label and accessibility flag and do this instead:
physicianCollectionView.accessibilityIdentifier = #"PhysicianCollectionParentView";
physicianCollectionView.isAccessibilityElement = NO;
For last elements in element tree, which do not have sub views, set isAccessibilityElement = YES;
If you haven't tried it already, you might try adding a delay at the beginning of your script:
UIATarget.localTarget().delay(3);
I think that it is possible that your app isn't done rendering/animating before the logElementTree() that you have posted above. We add delays at the beginning and between each application transition in our automated testing scripts to ensure that the screen has finished rendering.
EDIT: After messing around with your setup in a test app, I believe that your issue is that you are enabling Accessibility on the UIView that contains your segmented control. With accessibility disabled on the UIView, I am able to get the UISegmentedControl to show in the element tree, but once I enable it, the UIView then begins displaying as a UIAElement with no children. My suggestion is to disable accessibility for the containing UIView, and only use accessibility for base controls (like buttons, table view cells, or your segmented button control).

How do I set the accessibility label for a particular segment of a UISegmentedControl?

We use KIF for our functional testing, and it uses the accessibility label of elements to determine where to send events. I'm currently trying to test the behaviour of a UISegmentedControl, but in order to do so I need to set different accessibility labels for the different segments of the control. How do I set the accessibility label for a particular segment?
As Vertex said,
obj-c
[[[self.segmentOutlet subviews] objectAtIndex:3] setAccessibilityLabel:#"GENERAL_SEGMENT"];
swift
self.segmentOutlet.subviews[3].accessibilityLabel = "GENERAL_SEGMENT"
some advice so you don't go crazy like I did:
To scroll in accessibility mode swipe three fingers
The indexes of the segments are backwards than you would expect, i.e. the furthest segment to the right is the 0th index and the furthest to the left is the n'th index where n is the number of elements in the UISegmentControl
I'm just getting started with KIF myself, so I haven't tested this, but it may be worth a try. I'm sure I'll have the same issue soon, so I'd be interested to hear if it works.
First, UIAccessibility Protocol Reference has a note under accessibilityLabel that says:
"If you supply UIImage objects to display in a UISegmentedControl, you can set this property on each image to ensure that the segments are properly accessible."
So, I'm wondering if you could set the accessibilityLabel on each NSString object as well and be able to use that to access each segment with KIF. As a start, you could try creating a couple of strings, setting their accessibility labels, and using [[UISegmentedControl alloc] initWithItems:myStringArray]; to populate it.
Please update us on your progress. I'd like to hear how this goes
Each segment of UISegmentedControl is UISegment class instance which subclass from UIImageView. You can access those instances by subviews property of UISegmentedControl and try to add accessibility for them programmatically.
You can't rely on the index in the subviewsarray for the position. For customisation of the individual subviews I sort the subviews on their X Position before setting any propery.What would also be valid for accesibilityLbel.
let sortedViews = self.subviews.sorted( by: { $0.frame.origin.x < $1.frame.origin.x } )
sortedViews[0].accessibilityLabel = "segment_full"
sortedViews[1].accessibilityLabel = "segment_not_full"
This is an old question but just in case anyone else runs up against this I found that the segments automatically had an accessibility label specified as their text. So if two options were added of Option 1 and Option 2. A call to
[tester tapViewWithAccessibilityLabel:#"Option 2"];
successfully selected the segment.
The solutions with using an indexed subview is not working since you cannot rely on a correct order and it will be difficult to change the number of segments. And sorting by origin does not work, since the frame (at least for current versions) seems to be always at x: 0.
My solution:
(segmentedControl.accessibilityElement(at: 0) as? UIView)?.accessibilityLabel = "Custom VoiceOver Label 1"
(segmentedControl.accessibilityElement(at: 1) as? UIView)?.accessibilityLabel = "Custom VoiceOver Label 2"
(segmentedControl.accessibilityElement(at: 2) as? UIView)?.accessibilityLabel = "Custom VoiceOver Label 3"
Seems to work for me and has the correct order. You also do not rely on an image. Not that pretty either but maybe more reliable than other solutions.
This is an old question but just in case anyone else runs up against this I found that the segments automatically had an accessibility label specified as their text.
Further to Stuart's answer, I found it really useful when writing test cases to turn on 'Accessibility Inspector' on the Simulator (Settings -> General -> Accessibility -> Accessibility Inspector). You'd be surprised how many elements already have accessibility labels included, like in the standard iOS UI elements or even third party frameworks.
Note: Gestures will now be different - Tap to view accessibility information, double tap to select. Minimizing the Accessibility Inspector window (by tapping the X button) will return the gestures back to normal.
You guys want to see how Apple recommends it be done?
It's FUGLY.
This is from this example:
func configureCustomSegmentsSegmentedControl() {
let imageToAccessibilityLabelMappings = [
"checkmark_icon": NSLocalizedString("Done", comment: ""),
"search_icon": NSLocalizedString("Search", comment: ""),
"tools_icon": NSLocalizedString("Settings", comment: "")
]
// Guarantee that the segments show up in the same order.
var sortedSegmentImageNames = Array(imageToAccessibilityLabelMappings.keys)
sortedSegmentImageNames.sort { lhs, rhs in
return lhs.localizedStandardCompare(rhs) == ComparisonResult.orderedAscending
}
for (idx, segmentImageName) in sortedSegmentImageNames.enumerated() {
let image = UIImage(named: segmentImageName)!
image.accessibilityLabel = imageToAccessibilityLabelMappings[segmentImageName]
customSegmentsSegmentedControl.setImage(image, forSegmentAt: idx)
}
customSegmentsSegmentedControl.selectedSegmentIndex = 0
customSegmentsSegmentedControl.addTarget(self,
action: #selector(SegmentedControlViewController.selectedSegmentDidChange(_:)),
for: .valueChanged)
}
They apply the accessibility labels to images, and then attach the images. Not too different from the above answer.
another option if not willing to set accesibility label might be calculating the poistion of each segment part and use
[tester tapScreenAtPoint:segementPosition];
to trigger the actions
If you look at the segmented control thru the accessibility inspector, you find that the segments are UISegment objects. Moreover, they turn out to be direct subviews of the UISegmentedControl. That fact suggests the following insanely crazy but perfectly safe Swift 4 code to set the accessibility labels of the segments of a UISegmentedControl:
let seg = // the UISegmentedControl
if let segclass = NSClassFromString("UISegment") {
let segments = seg.subviews.filter {type(of:$0) == segclass}
.sorted {$0.frame.minX < $1.frame.minX}
let labels = ["Previous article", "Next article"] // or whatever
for pair in zip(segments,labels) {
pair.0.accessibilityLabel = pair.1
}
}
As mentioned in the accepted answer, adding accessibilityLabel to the text should do the trick:
let title0 = "Button1" as NSString
title0.accessibilityLabel = "MyButtonIdentifier1"
segmentedControl.setTitle("\(title0)", forSegmentAt: 0)
let title1 ="Button2" as NSString
title1.accessibilityLabel = "MyButtonIdentifier2"
segmentedControl.setTitle("\(title1)", forSegmentAt: 1)
XCode 12 / iOS 14.3 / Swift 5
This is an old post but I encountered the same problem trying to set accessibility hints for individual segments in a UISegmentedControl. I also had problems with some of the older solutions. The code that's currently working for my app borrows from replies such as those from matt and Ilker Baltaci and then mixes in my own hack using UIView.description.
First, some comments:
For my UISegmentedControl with 3 segments, the subview count is 3 in the viewDidLoad() and viewWillAppear() of the parent UIVIewController. But the subview count is 7 in viewDidAppear().
In viewDidLoad() or viewWillAppear() the subview frames aren't set, so ordering the subviews didn't work for me. Apparently Benjamin B encountered the same problem with frame origins.
In viewDidAppear(), the 7 subviews include 4 views of type UIImageView and 3 views of type UISegment.
UISegment is a private type. Working directly with the private API might flag your app for rejection. (see comment below)
type(of:) didn't yield anything useful for the UISegment subviews
(HACK!) UIView.description can be used to check the type without accessing the private API.
Setting accessibility hints based on X order tightly couples UI segment titles and hints to their current positions. If user testing suggests a change in segment order, then changes must be made both in the UI and in the code to set accessibility hints. It's easy to miss that.
Using an enum to set segment titles is an alternative to relying on X ordering set manually in the UI. If your enum inherits from String and adopts the protocols CaseIterable and RawRepresentable, then it's straightforward to create titles from the enum cases, and to determine the enum case from a segment title.
There's no guarantee the following will work in a future release of the framework, given the reliance on description.contains("UISegment") but it's working for me. Gotta move on.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// get only the UISegment items; ignore UIImageView
let segments = mySegmentedControl.subviews.compactMap(
{ $0.description.contains("UISegment") ? $0 : nil }
)
let sortedSegments = segments.sorted(
by: { $0.frame.origin.x < $1.frame.origin.x }
)
for i in 0 ..< sortedSegments.count {
let segment = sortedSegments[i]
// set .accessibilityHint or .accessibilityLabel by index
// or check for a segment title matching an enum case
// ...
}
}
On Private APIs and Rejection
I'm referring to the April 2016 comment from #dan in Test if object is an instance of class UISegment:
It's a private class. You can check it with [...
isKindOfClass:NSClassFromString(#"UISegment")] but that may get your
app rejected for using private api or stop working in the future if
apple changes the internal class name or structure.
Also:
What exactly is a Private API, and why will Apple reject an iOS App if one is used?
"App rejected due to non-public api's": https://discussions.apple.com/thread/3838251
As Vortex said, the array is right to left with [0] starting on the right. You can set every single accessibility option by accessing the subviews. Since the subviews are optional, it is good to pull out the subview first, and then assign the accessibility traits that you want. Swift 4 example for a simple two option segment control:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let rightSegment = segmentControl.subviews.first, let leftSegment = segmentControl.subviews.last else { return }
rightSegment.accessibilityLabel = "A label for the right segment."
rightSegment.accessibilityHint = "A hint for the right segment."
leftSegment.accessibilityLabel = "A label for the left segment."
leftSegment.accessibilityHint = "A hint for the left segment."
}

Resources