Change order of read items with VoiceOver - ios

I have a bunch of buttons on the screen which are positioned intuitively visually but are not read in an intuitive order by VoiceOver. This is because certain buttons like Up and Down are placed above and below each other. However, voiceover starts reading from Left to Right, from Top to Bottom, it seems.
This results in voiceover reading the button to the right of "Up" after "Up", instead of reading "Down" immediately afterward.
How do I force voiceover to read the button that I want to read? I should mention that I'm using the swipe-to-cycle-through-elements feature on voiceover.
All my buttons are subclassed versions of UIView and UIButton. Here's an example of a button initiator I use. Ignore the pixel count - I know that's bad form but I'm in a pinch at the moment:
UIButton* createSpecialButton(CGRect frame,
NSString* imageName,
NSString* activeImageName,
id target,
SEL obClickHandler)
{
UIButton* b = [UIButton buttonWithType:UIButtonTypeCustom];
[b setImage:[GlobalHelper nonCachedImage:imageName ofType:#"png"]
forState:UIControlStateNormal];
[b setImage:[GlobalHelper nonCachedImage:activeImageName ofType:#"png"]
forState:UIControlStateHighlighted];
[b addTarget:target action:obClickHandler forControlEvents:UIControlEventTouchUpInside];
b.frame= frame;
return b;
}
- (UIButton *) createSendButton {
CGFloat yMarker = 295;
UIButton* b = createSpecialButton(CGRectMake(160, yMarker, 70, 45),
#"Share_Btn",
#"Share_Selected_Btn",
self,
#selector(sendAction));
b.accessibilityHint = #"Send it!";
b.accessibilityLabel = #"Stuff for voiceover to be added";
[self.view addSubview:b];
return b;
}

You can change the order by setting the view's accessibilityElements array:
self.view.accessibilityElements = #[self.view1, self.view2, self.view3, self.view4];
or
self.anotherView.accessibilityElements = #[self.label1, self.txtView1, self.label2, self.txtView2];
If you need to enable user interaction programmatically:
[self.view1 setUserInteractionEnabled:YES];
Note: If the view is hidden, VoiceOver will not pass through it.

The easiest answer to this lies in creating a UIView subclass that contains your buttons, and responds differently to the accessibility calls from the system. These important calls are:
-(NSInteger)accessibilityElementCount
-(id)accessibilityElementAtIndex:
-(NSInteger)indexOfAccessibilityElement:
I've seen a few of these questions, and answered one before, but I've not seen a generic example of how to reorder the VoiceOver focus. So here is an example of how to create a UIView subclass that exposes its accessible subviews to VoiceOver by tag.
AccessibilitySubviewsOrderedByTag.h
#import <UIKit/UIKit.h>
#interface AccessibilitySubviewsOrderedByTag : UIView
#end
AccessibilitySubviewsOrderedByTag.m
#import "AccessibilityDirectional.h"
#implementation AccessibilitySubviewsOrderedByTag {
NSMutableArray *_accessibilityElements;
}
//Lazy loading accessor, avoids instantiating in initWithCoder, initWithFrame, or init.
-(NSMutableArray *)accessibilityElements{
if (!_accessibilityElements){
_accessibilityElements = [[NSMutableArray alloc] init];
}
return _accessibilityElements;
}
// Required accessibility methods...
-(BOOL)isAccessibilityElement{
return NO;
}
-(NSInteger)accessibilityElementCount{
return [self accessibilityElements].count;
}
-(id)accessibilityElementAtIndex:(NSInteger)index{
return [[self accessibilityElements] objectAtIndex:index];
}
-(NSInteger)indexOfAccessibilityElement:(id)element{
return [[self accessibilityElements] indexOfObject:element];
}
// Handle added and removed subviews...
-(void)didAddSubview:(UIView *)subview{
[super didAddSubview:subview];
if ([subview isAccessibilityElement]){
// if the new subview is an accessibility element add it to the array and then sort the array.
NSMutableArray *accessibilityElements = [self accessibilityElements];
[accessibilityElements addObject:subview];
[accessibilityElements sortUsingComparator:^NSComparisonResult(id obj1, id obj2){
// Here we'll sort using the tag, but really any sort is possible.
NSInteger one = [(UIView *)obj1 tag];
NSInteger two = [(UIView *)obj2 tag];
if (one < two) return NSOrderedAscending;
if (one > two) return NSOrderedDescending;
return NSOrderedSame;
}];
}
}
-(void)willRemoveSubview:(UIView *)subview{
[super willRemoveSubview:subview];
// Clean up the array. No check since removeObject: is a safe call.
[[self accessibilityElements] removeObject:subview];
}
#end
Now simply enclose your buttons in an instance of this view, and set the tag property on your buttons to be essentially the focus order.

In Swift you just have to set view's accessiblityElements array property:
view.accessibilityElements = [view1, view2, view3] // order you wish to have

I know this is an old thread, but I found that the easiest way to do it is to subclass UIView. Then simply modify your main UIView type in storyboard to AccessibiltySubviewsOrderedByTag and update the tags in each subview you want to read in order.
class AccessibilitySubviewsOrderedByTag: UIView {
override func layoutSubviews() {
self.accessibilityElements = [UIView]()
for accessibilitySubview in self.subviews {
if accessibilitySubview.isAccessibilityElement {
self.accessibilityElements?.append(accessibilitySubview)
}
}
self.accessibilityElements?.sort(by: {($0 as AnyObject).tag < ($1 as AnyObject).tag})
}
}

This doesn’t directly answer the original question, but it answers the title of the question:
When I want VoiceOver to swipe down a column, I have been using a containing view for the column with shouldGroupAccessibilityChildren set.
I wish I had known this earlier, because it can be a pain to retroactively insert containers into an autolayout situation…

I tried Wesley's answer of setting the array of the accessibilityElements but it didn't work for me.
Apple has some documentation Enhancing the Accessibility of Table View Cells with an example in code. Basically you set the accessibility label of the cell (the parent view) to the values of the accessibility labels of the child views.
[cell setAccessibilityLabel:[NSString stringWithFormat:#"%#, %#", cityLabel, temperatureLabel]];
This is what worked for me.

I found a convenience way yesterday. Similar to #TejAces ' answer.
Make a new swift file, then copy these things into it.
import UIKit
extension UIView {
func updateOrder(_ direction: Bool = true) {
var tempElements: [Any]? = [Any]()
let views = (direction) ? subviews : subviews.reversed()
for aView in views {
tempElements?.append(aView)
}
accessibilityElements = tempElements
}
}
class ReorderAccessibilityByStoryBoardView: UIView {
override func didAddSubview(_ subview: UIView) {
updateOrder()
super.didAddSubview(subview)
}
}
Set the UIView(contains views you want to reorder)'s class as ReorderAccessibilityByStoryBoardView. Then you can reorder them by reordering storyboard's view list.
Because subview doesn't contain views in StackView/ScrollView, you need to make a independent class in this file. Such as the ReorderAccessibilityByStoryBoardStackView down below.
class ReorderAccessibilityByStoryBoardStackView: UIStackView {
override func didAddSubview(_ subview: UIView) {
updateOrder(false)
super.didAddSubview(subview)
}
}
With these codes, you can also reorder view's added in code by adding them in a specific order.

I think you can do it in the storyboard. The VoiceOver order is determined by the order of the views in the document outline.
Just drag and drop the views in the view hierarchy in the right order.
Edit:
Sorry I can not post screenhots until 10 reputation. In the storyboard, the document outline is the area on the left where your scenes with their subviews are listed. Here, subviews are ordered one below each other. When you change this order, the reading-order of VoiceOver will change.

Swift 5.x
Following the advice of ChrisJF , I've wrote a little extension to bypass the Apple bug around the correct order reading items.
extension UIView {
func setAccessibilityOrder(_ arrayViews:[Any]?){
self.accessibilityElements = arrayViews
let arrayStrings:[String] = arrayViews?.map { String(($0 as AnyObject).accessibilityLabel ?? "") } ?? []
let formatList = arrayStrings.map { _ in "%#" }.joined(separator: ", ")
self.accessibilityLabel = String(format: formatList, arguments:arrayStrings)
self.isAccessibilityElement = true
}
}
Usage:
view1.accessibilityLabel = "my view 1"
label2.accessibilityLabel = "my label 2"
button3.accessibilityLabel = "my button 3"
let order = [view1, label2, button3]
self.setAccessibilityOrder(order) // or self.view.setAccessibilityOrder(order) if you are on a parent controller

Related

UISearchBar wont remove UISearchBarBackground

I have tried EVERYTHING to get this to work. I setup a custom class like so.
override func layoutSubviews() {
super.layoutSubviews()
clearBackgroundColor() // function in the question
}
private func clearBackgroundColor() {
guard let UISearchBarBackground: AnyClass = NSClassFromString("UISearchBarBackground") else { return }
for view in self.subviews {
for subview in view.subviews {
if subview.isKind(of: UISearchBarBackground) {
subview.alpha = 0
}
}
}
}
I set backgroundColor, barTintColor to .clear. Style to minimal. Im losing my mind. I set breakpoints to make sure we are finding the search bar background. Ive tried subview.removeFromSuperview() as well. Nothing. I think Im going insane. Am I missing something?
This is on iOS 10 and am using storyboard. Any help would be greatly appreciated.
I had to do this in a client's app a while ago. Here's what worked for me:
I had a UISearchBar subclass:
#property (nonatomic, strong) UITextField* textField;
I called the following from init:
self.textField = [self findViewOfClass:[UITextField class] inView:self];
self.translucent = NO;
self.barTintColor = ...;
self.textField.backgroundColor = ...;
- (id)findViewOfClass:(Class)class inView:(UIView*)view
{
if ([view isKindOfClass:class])
{
return view;
}
else
{
for (UIView* subview in view.subviews)
{
id foundView = [self findViewOfClass:class inView:subview];
if (foundView != nil)
{
return foundView;
}
}
}
return nil;
}
The essential part is finding the UITextField. (I did a similar thing to allow me to custom style the cancel button.) I vaguely remember that disabling translucent was really needed; easy to try.
That should be it. Let me know if this works for you.
I only have Obj-C code, but this is easy to convert.
I finally ignored previous answers from all the posts about this subject and did my own Debug View Hierarchy. I spotted a ImageView that serves as the background which I guess is now called "_UISearchBarSearchFieldBackgroundView". This helped me find a single function that fixes the problem at least for iOS 9+.
searchBar.setSearchFieldBackgroundImage(UIImage(), for: .normal)
One thing to note is that this isn't the only way to fix this problem. However, I used it because it requires no looping and because the image is empty the additional view is never added giving the same end result as other methods.
One thing to note is that this may only work for iOS 9+. So, your milage may vary. I tested with iOS 10 with a Deployment Target of 9.3.

Unable to set accessibilityIdentifier of UISegmentedControl's segments

I found out, that even though I could set accessibilityLabel of UISegmentedControl's segment (see: How do I set the accesibility label for a particular segment of a UISegmentedControl?), I couldn't set accessibilityIdentifier, which was equally important for my project. I need to target a segment irrespective of its text and accessibilityLabel for automation purposes.
For example, the code:
NSString *name = [NSString stringWithFormat:#"Item %li", (long)idx];
segment.accessibilityIdentifier = name;
NSLog(#"ID: %#", segment.accessibilityIdentifier);
results in:
ID: (null)
No exceptions are thrown.
Does anybody have insight into why accessibilityLabel is implemented, but not accessibilityIdentifier?
I got around this issue by writing a Swift extension for XCUIElement that added a new method tap(at: UInt). This method gets the buttons query of the element and sort the results based on their x position. This allows us to specify which segment of the UISegmentedControl we want to tap rather than relying on the button text.
extension XCUIElement {
func tap(at index: UInt) {
guard buttons.count > 0 else { return }
var segments = (0..<buttons.count).map { buttons.element(boundBy: $0) }
segments.sort { $0.0.frame.origin.x < $0.1.frame.origin.x }
segments[Int(index)].tap()
}
}
Here is an example of looping through the views to set the accessibilityIdentifier by referencing the segment title.
Unfortunately when you set the identifier it doesn't persist. UISegments must be doing some tricky overriding. Still at a loss for how to get this to work.
extension UISegmentedControl {
/// Sets accessibility for segment buttons
func setAccessibilityIdentifier(_ accessibilityIdentifier: String, for segmentTitle: String) {
guard let segment = subviews.first(where: {
$0.subviews.contains(where: { ($0 as? UILabel)?.text == Optional(segmentTitle) })
}) else { return }
segment.accessibilityIdentifier = accessibilityIdentifier
}
}
I had tested the following code with Xcode 5.1.1 and iOS Simulator 7.1:
UISegmentedControl *contol = [[UISegmentedControl alloc] initWithItems:
#[#"0", #"1"]];
[self.view addSubview:contol];
UIView *segment = [[contol subviews] firstObject];
segment.accessibilityIdentifier = #"Item 0";
NSLog(#"ID: %#", segment.accessibilityIdentifier);
it didn't work for iPhone Retina (3.5-inch) and iPhone Retina (4-inch) i.e. result was:
ID: (null)
but it worked for iPhone Retina (4-inch 64-bit) i.e. result was:
ID: Item 0
Then I've replaced #[#"0", #"1"] with #[#"", #""] in UISegmentedControl initialization and the code worked for all mentioned platforms.
It appears, both accessibilityIdentifier and accessibilityLabel are implemented, but somehow the initial values of UISegmentedControl interfere with accessibilityIdentifiers of its segments.
I implemented this workaround and got automation to work (with KIF).
Code is in Swift, works for me with Xcode 6.1.1, iOS 8 SDK
for index in 0..<segmentedControl.numberOfSegments {
let identifierView = UIView()
identifierView.accessibilityIdentifier = "Item \(index)"
(segmentedControl.subviews[index] as UIView).addSubview(identifierView)
}
I only had images, without any labels, so I used the code below. I found the indexes didn't correspond to the order on screen, so I keyed off of the initial accessibilityLabel values, which were the names of the images I specified in Interface Builder.
override func viewDidLoad() {
super.viewDidLoad()
for segment in segmentedControl.subviews {
switch segment.accessibilityLabel {
case .Some("First Image"):
segment.accessibilityLabel = "Description of first item"
break
case .Some("Second Image"):
segment.accessibilityLabel = "Description of second item"
break
default:
NSLog("Unknown accessibility label: \(segment.accessibilityLabel)")
break
}
}
}
I ran into this. Some of the previous answers didn't seem to address the accessibilityIdentifier at all. I did try Jawwad's approach of accessing the segments and adding a new UIView and setting the accessibilityIdentifier on that. However, I'm using EarlGrey for UI Testing and unfortunately, when it tried to tap on that view, it didn't work. However, based on this I did the following variation which DID work.
The trick is to enumerate the segments (as per Jawwad) and then for each, find the UIImageView subview and set its accessibilityID.
This works for lookup and interaction.
let ids = ["Identifier1", "Identifier2"]
for index in 0..<segmentedControl.numberOfSegments {
if let view = p.subviews[index] as? UIView {
for v in view.subviews {
// Setting the ID twice would cause EarlGrey tests to fail
if let iv = v as? UIImageView, iv.accessibilityIdentifier == nil {
iv.accessibilityIdentifier = ids[index]
break
}
}
}
}
I was able to work around the issue by overriding UIView's NSArray *accessibilityElements property, and adding accessibilityIdentifier to each of the returned UISegment.
- (NSArray *)accessibilityElements {
NSArray *elements = [super accessibilityElements];
[elements enumerateObjectsUsingBlock:^(UIView *obj, NSUInteger idx, BOOL *stop) {
obj.accessibilityIdentifier = self.identifiers[idx].accessibilityIdentifier;
}];
return elements;
}

Turning off dimming by UIPopoverController

In iOS7, a popover causes the rest of the screen to be dimmed. As per the Apple docs:
The popover content is layered on top of your existing content and the background is dimmed automatically.
This is nice in most cases, but I have an app where the screen rearranges itself when the popover opens and stays responsive, so the dimming only causes confusion. Anyone knows if dimming can be disabled?
Doesn’t look like there’s anything in the API to support that—you can set the passthroughViews property to allow other views to be interacted with while the popover’s open, but that doesn’t affect the dimming. You may have to roll your own popover implementation or find a third-party version.
I can suggest you a custom control which is really nice work by its author. It do not dim the background. Further it has many customization.
Here is the github link for WYPopoverController
For me at works like this. I just work through all subviews if key window view, find _UIMirrorNinePatchView. _UIMirrorNinePatchView is apple class for that has four image views, these image views create the dimming background for 4 directions of PopOverPresentationController. More specifically you can look at this if you use view hierarchy debugger. So I walk through the array of these UIImageView and set UIImage to nil. This code paste in viewWillAppear of your destination controller(popOverContoller).
NSArray<UIView *> *arrayOfSubviews = [UIApplication sharedApplication].keyWindow.subviews.lastObject.subviews;
for (int i = 0; i < arrayOfSubviews.count; i++) {
if ([NSStringFromClass(arrayOfSubviews[i].class) isEqualToString:#"_UIMirrorNinePatchView"]) {
arrayOfSubviews[i].backgroundColor = [UIColor clearColor];
NSArray<UIImageView *> *arrayOfImageViews = arrayOfSubviews[i].subviews;
for (int j = 0; j < arrayOfImageViews.count; j++) {
arrayOfImageViews[j].image = nil;
}
}
}
In whole my UIPopOverController looks like this
And in view debugger, it looks so
So as you can understand, setting UIImage to nil will remove this dimming view.
This is the swift version to remove the dimming of UIPopoverController
let allSubViews: [UIView] = (UIApplication.shared.keyWindow?.subviews.last?.subviews)!
for index in 0...allSubViews.count - 1 {
allSubViews[index].removeFromSuperview()
if NSStringFromClass(allSubViews[index].classForCoder) == "_UIMirrorNinePatchView"
{
allSubViews[index].backgroundColor = UIColor.clear
let arrayofImages = allSubViews[index].subviews as! [UIImageView]
for imageIndex in 0...arrayofImages.count - 1 {
arrayofImages[imageIndex].image = nil
}
}
}
You can prevent the dimming by setting the UIPopoverBackgroundView for your popover and setting the background to be transparent for the background view.
You will need to re-implement how the popover draws the arrows, but you can find plenty of examples for that online.
Updated to work in iOS 13 with Swift 4
guard let transitionSubviews = UIApplication.shared.keyWindow?.subviews.last?.subviews else { return }
func findViews<T>(inView view: UIView, subclassOf targetType: T.Type) -> [T] {
return recursiveSubviews(inView: view).compactMap { $0 as? T }
}
func recursiveSubviews(inView view: UIView) -> [UIView] {
return view.subviews + view.subviews.flatMap { recursiveSubviews(inView: $0) }
}
for view in transitionSubviews {
view.backgroundColor = UIColor.clear
for imageView in findViews(inView: view, subclassOf: UIImageView.self) {
imageView.image = nil
}
}
If you choose to implement your custom UIPopoverBackgroundView, you can set the layer background to be clear - layer.shadowColor = UIColor.clearColor().CGColor.
However this will eliminate the dim and the shadow completely so you will have to put a border around the controller
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
This solved my problem with navigation bar dimming effect while transiting.

ios- is "swapping" UIViews possible?

Here's my code:
if([pantallas objectForKey:par]){
UIView *vista= [[UIView alloc] initWithFrame:self.Botones.frame];
vista.backgroundColor= [UIColor brownColor];
CGSize la= CGSizeMake(50,60);
int cuantos= [part2 count];
NSArray *arr= [COLGenerales tileN:cuantos RectsOfSize:la intoSpaceOf:vista.frame withMaxPerRow:5 spaceVertical:10 spaceHorizontal:10];
for(int j=0; j<cuantos; j++){
UIButton *bot= [[UIButton alloc] initWithFrame:[[arr objectAtIndex:j] CGRectValue]];
bot.tag=j;
bot.titleLabel.text=par;
bot.titleLabel.hidden=true;
bot.imageView.image = [UIImage imageNamed:[[part2 allKeys] objectAtIndex:j]];
[bot addTarget:self action:#selector(registrar:) forControlEvents:UIControlEventTouchDown];
[vista addSubview:bot];
}
[pantallas setObject:vista forKey:par];
self.Botones= vista;
}else{
self.Botones= [pantallas objectForKey:par];
}
Botones is a simple view embedded into the view this class controls (first initiated by the Nib file), the class method of COLGenerales returns an array of CGRects coded as NSValues, and registrar: is a local method.
Everything gets properly set (I've thoroughly checked this with the debugger). The view gets successfully created, set, and added to the dictionary.
However, I absolutely never get the actual screen to change. I even included the background color change just to check if it isn't some kind of problem with the buttons. Nothing. Any suggested solution to this?
A property that is an IBOutlet does not have an intrinsic connection to the view hierarchy—it only makes it possible to populate that property from a xib. When you set self.Botones, you'll need to do something like the following:
[self.Botones removeFromSuperview];
self.Botones = newValue;
[self.BotonesSuperview addSubview:self.Botones];
If you update self.Botones in many places, and you always want the change reflected on-screen, you could add this into a setter implementation:
-(void)setBotones:(UIView*)newValue {
if (newValue != _Botones) {
[_Botones removeFromSuperview];
_Botones = newValue;
[self.BotonesSuperview addSubview:_Botones];
}
}
I recommend using a UINavigation controller that houses these two views.
You can reference this link Swapping between UIViews in one UIViewController
Basically, you create one view, removeSubview for the first and then add the second one with addSubview!
[view1 removeFromSuperview];
[self.view addSubview: view2];
Other reference sources:
An easy, clean way to switch/swap views?
How to animate View swap on simple View iPhone App?
Hopefully this helps!

How to remove all subviews?

When my app gets back to its root view controller, in the viewDidAppear: method I need to remove all subviews.
How can I do this?
Edit: With thanks to cocoafan: This situation is muddled up by the fact that NSView and UIView handle things differently. For NSView (desktop Mac development only), you can simply use the following:
[someNSView setSubviews:[NSArray array]];
For UIView (iOS development only), you can safely use makeObjectsPerformSelector: because the subviews property will return a copy of the array of subviews:
[[someUIView subviews]
makeObjectsPerformSelector:#selector(removeFromSuperview)];
Thank you to Tommy for pointing out that makeObjectsPerformSelector: appears to modify the subviews array while it is being enumerated (which it does for NSView, but not for UIView).
Please see this SO question for more details.
Note: Using either of these two methods will remove every view that your main view contains and release them, if they are not retained elsewhere. From Apple's documentation on removeFromSuperview:
If the receiver’s superview is not nil, this method releases the receiver. If you plan to reuse the view, be sure to retain it before calling this method and be sure to release it as appropriate when you are done with it or after adding it to another view hierarchy.
Get all the subviews from your root controller and send each a removeFromSuperview:
NSArray *viewsToRemove = [self.view subviews];
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
In Swift you can use a functional approach like this:
view.subviews.forEach { $0.removeFromSuperview() }
As a comparison, the imperative approach would look like this:
for subview in view.subviews {
subview.removeFromSuperview()
}
These code snippets only work in iOS / tvOS though, things are a little different on macOS.
If you want to remove all the subviews on your UIView (here yourView), then write this code at your button click:
[[yourView subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
This does only apply to OSX since in iOS a copy of the array is kept
When removing all the subviews, it is a good idea to start deleting at the end of the array and keep deleting until you reach the beginning. This can be accomplished with this two lines of code:
for (int i=mySuperView.subviews.count-1; i>=0; i--)
[[mySuperView.subviews objectAtIndex:i] removeFromSuperview];
SWIFT 1.2
for var i=mySuperView.subviews.count-1; i>=0; i-- {
mySuperView.subviews[i].removeFromSuperview();
}
or (less efficient, but more readable)
for subview in mySuperView.subviews.reverse() {
subview.removeFromSuperview()
}
NOTE
You should NOT remove the subviews in normal order, since it may cause a crash if a UIView instance is deleted before the removeFromSuperview message has been sent to all objects of the array. (Obviously, deleting the last element would not cause a crash)
Therefore, the code
[[someUIView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
should NOT be used.
Quote from Apple documentation about makeObjectsPerformSelector:
Sends to each object in the array the message identified by a given
selector, starting with the first object and continuing through the
array to the last object.
(which would be the wrong direction for this purpose)
Try this way swift 2.0
view.subviews.forEach { $0.removeFromSuperview() }
view.subviews.forEach { $0.removeFromSuperview() }
Use the Following code to remove all subviews.
for (UIView *view in [self.view subviews])
{
[view removeFromSuperview];
}
Using Swift UIView extension:
extension UIView {
func removeAllSubviews() {
for subview in subviews {
subview.removeFromSuperview()
}
}
}
In objective-C, go ahead and create a category method off of the UIView class.
- (void)removeAllSubviews
{
for (UIView *subview in self.subviews)
[subview removeFromSuperview];
}
In order to remove all subviews Syntax :
- (void)makeObjectsPerformSelector:(SEL)aSelector;
Usage :
[self.View.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
This method is present in NSArray.h file and uses NSArray(NSExtendedArray) interface
If you're using Swift, it's as simple as:
subviews.map { $0.removeFromSuperview }
It's similar in philosophy to the makeObjectsPerformSelector approach, however with a little more type safety.
For ios6 using autolayout I had to add a little bit of code to remove the constraints too.
NSMutableArray * constraints_to_remove = [ #[] mutableCopy] ;
for( NSLayoutConstraint * constraint in tagview.constraints) {
if( [tagview.subviews containsObject:constraint.firstItem] ||
[tagview.subviews containsObject:constraint.secondItem] ) {
[constraints_to_remove addObject:constraint];
}
}
[tagview removeConstraints:constraints_to_remove];
[ [tagview subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
I'm sure theres a neater way to do this, but it worked for me. In my case I could not use a direct [tagview removeConstraints:tagview.constraints] as there were constraints set in XCode that were getting cleared.
In monotouch / xamarin.ios this worked for me:
SomeParentUiView.Subviews.All(x => x.RemoveFromSuperview);
In order to remove all subviews from superviews:
NSArray *oSubView = [self subviews];
for(int iCount = 0; iCount < [oSubView count]; iCount++)
{
id object = [oSubView objectAtIndex:iCount];
[object removeFromSuperview];
iCount--;
}

Resources