I'm trying programmatically implement this layout:
[label|label|slider|label|textField]
[label|label|slider|label|textField]
[label|label|slider|label|textField]
this is vertical layout with horizontal layouts inside
First problem, what will be the slider size? Unknown, so I add width constraint:
[stackView addConstraint:[NSLayoutConstraint constraintWithItem:slider
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:150]];
but i'm not sure if it's ok to add constrain in stackView, correct me please if I'm wrong.
Vertical stackView have Leading alignment, Horizontal - Center alignment, here is build result
I want to align everything left, but I don't know how, I can do this using IB but the same settings programmatically not working :(
P.S. Also sometimes I'm getting this in log:
Failed to rebuild layout engine without detectable loss of precision. This should never happen. Performance and correctness may suffer.
UIStackView is great, except when it's not.
To use the approach you're taking - a Vertical UIStackView with "rows" of Horizontal stack views - you'll need to explicitly set a width constraint on every element.
Without explicit widths, you get:
|Description Label|Val-1|--slider--|Val-2|Input|
|Short label|123|--slider--|123|42.6623|
|A much longer label|0|--slider--|0|-42.6623|
As you can see, the first element in each row - the Description Label - does not have an equal width to the other row's Description Labels, and the separate stack views will arrange them based on their intrisic widths. In this case, the length of the text.
So, you either need to decide ahead of time that Description Label has a width of, say, 80; Val-1 has a width of 40; Slider has a width of 150; Val-2 has a width of 80; and Input has a width of 100.
Now you'll be able to get:
|Description Label |Val-1|--slider--|Val-2| Input |
|Short label | 123 |--slider--| 123 | 42.6623 |
|A much longer label | 0 |--slider--| 0 | -42.6623 |
I don't think you want a width constraint on the slider. Looking at your screen shot, I guess you probably want this:
So we have some parameters. Each parameter has a name, a minimum value, a maximum value, and a current value. For each parameter, we have one row showing all of the parameter's properties, with the current value shown as both a slider and a text field. The name labels all have the same width, which is the narrowest width that doesn't clip any of the labels. Same for the minimum and maximum value labels. The value text fields all have the same width, and it is the narrowest width that won't clip the value string even at the minimum or maximum value. The slider expands or contracts as needed to fill all remaining space in its row.
Here's how I did it, all in code.
First, I made a Parameter class:
#interface Parameter: NSObject
#property (nonatomic, copy, readonly, nonnull) NSString *name;
#property (nonatomic, readonly) double minValue;
#property (nonatomic, readonly) double maxValue;
#property (nonatomic) double value;
- (instancetype _Nonnull)initWithName:(NSString *_Nonnull)name minValue:(double)minValue maxValue:(double)maxValue initialValue:(double)value;
#end
#implementation Parameter
- (instancetype)initWithName:(NSString *)name minValue:(double)minValue maxValue:(double)maxValue initialValue:(double)value {
if (self = [super init]) {
_name = [name copy];
_minValue = minValue;
_maxValue = maxValue;
_value = value;
}
return self;
}
#end
Then I made a ParameterView class with this interface:
#interface ParameterView: UIStackView
#property (nonatomic, strong, readonly, nonnull) Parameter *parameter;
#property (nonatomic, strong, readonly, nonnull) UILabel *nameLabel;
#property (nonatomic, strong, readonly, nonnull) UILabel *minValueLabel;
#property (nonatomic, strong, readonly, nonnull) UISlider *valueSlider;
#property (nonatomic, strong, readonly, nonnull) UILabel *maxValueLabel;
#property (nonatomic, strong, readonly, nonnull) UITextField *valueTextField;
- (instancetype _Nonnull)initWithParameter:(Parameter *_Nonnull)parameter;
#end
Notice that ParameterView is a subclass of UIStackView. The initializer looks like this:
static void *kvoParameterValue = &kvoParameterValue;
#implementation ParameterView
- (instancetype)initWithParameter:(Parameter *)parameter {
if (self = [super init]) {
self.axis = UILayoutConstraintAxisHorizontal;
self.alignment = UIStackViewAlignmentFirstBaseline;
self.spacing = 2;
_parameter = parameter;
_nameLabel = [self pv_labelWithText:[parameter.name stringByAppendingString:#":"] alignment:NSTextAlignmentRight];
_minValueLabel = [self pv_labelWithText:[NSString stringWithFormat:#"%.0f", parameter.minValue] alignment:NSTextAlignmentRight];
_maxValueLabel = [self pv_labelWithText:[NSString stringWithFormat:#"%.0f", parameter.maxValue] alignment:NSTextAlignmentLeft];
_valueSlider = [[UISlider alloc] init];
_valueSlider.translatesAutoresizingMaskIntoConstraints = NO;
_valueSlider.minimumValue = parameter.minValue;
_valueSlider.maximumValue = parameter.maxValue;
_valueTextField = [[UITextField alloc] init];
_valueTextField.translatesAutoresizingMaskIntoConstraints = NO;
_valueTextField.borderStyle = UITextBorderStyleRoundedRect;
_valueTextField.text = [self stringWithValue:parameter.minValue];
CGFloat width = [_valueTextField systemLayoutSizeFittingSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].width;
_valueTextField.text = [self stringWithValue:parameter.maxValue];
width = MAX(width, [_valueTextField systemLayoutSizeFittingSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].width);
[_valueTextField.widthAnchor constraintGreaterThanOrEqualToConstant:width].active = YES;
[self addArrangedSubview:_nameLabel];
[self addArrangedSubview:_minValueLabel];
[self addArrangedSubview:_valueSlider];
[self addArrangedSubview:_maxValueLabel];
[self addArrangedSubview:_valueTextField];
[_parameter addObserver:self forKeyPath:#"value" options:0 context:kvoParameterValue];
[_valueSlider addTarget:self action:#selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
[self updateViews];
}
return self;
}
Notice that I set a greater-than-or-equal-to constraint on _valueTextField.widthAnchor, after finding the minimum width that can show both the minimum and maximum values without clipping.
I use a helper method to create each of the label values, since they are all created the same way:
- (UILabel *)pv_labelWithText:(NSString *)text alignment:(NSTextAlignment)alignment {
UILabel *label = [[UILabel alloc] init];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.text = text;
label.textAlignment = alignment;
[label setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 forAxis:UILayoutConstraintAxisHorizontal];
[label setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
return label;
}
What I'm doing here is setting the horizontal content compression resistance priority very high, so that none of the labels will clip its content. But then I'm setting the horizontal hugging priority fairly high (but not as high) so that each label will try to be as narrow as possible (without clipping).
To make the columns of labels line up, I need one more method:
- (void)constrainColumnsToReferenceView:(ParameterView *)referenceView {
[NSLayoutConstraint activateConstraints:#[
[_nameLabel.widthAnchor constraintEqualToAnchor:referenceView.nameLabel.widthAnchor],
[_minValueLabel.widthAnchor constraintEqualToAnchor:referenceView.minValueLabel.widthAnchor],
[_valueSlider.widthAnchor constraintEqualToAnchor:referenceView.valueSlider.widthAnchor],
[_maxValueLabel.widthAnchor constraintEqualToAnchor:referenceView.maxValueLabel.widthAnchor],
[_valueTextField.widthAnchor constraintEqualToAnchor:referenceView.valueTextField.widthAnchor],
]];
}
Since this creates constraints between the views in two different rows, I can't use it until both rows have a common superview. So I use it in my view controller's 'viewDidLoad`:
- (void)viewDidLoad {
[super viewDidLoad];
UIStackView *rootStack = [[UIStackView alloc] init];
rootStack.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:rootStack];
[NSLayoutConstraint activateConstraints:#[
[rootStack.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:8],
[rootStack.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:8],
[rootStack.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-8],
]];
rootStack.axis = UILayoutConstraintAxisVertical;
rootStack.spacing = 2;
rootStack.alignment = UIStackViewAlignmentFill;
ParameterView *firstParameterView;
for (Parameter *p in _parameters) {
ParameterView *pv = [[ParameterView alloc] initWithParameter:p];
[rootStack addArrangedSubview:pv];
if (firstParameterView == nil) {
firstParameterView = pv;
} else {
[pv constrainColumnsToReferenceView:firstParameterView];
}
}
}
And here is my complete ViewController.m file, in case you want to play with it. Just copy and paste it into a newly-created iOS project.
#import "ViewController.h"
#interface Parameter: NSObject
#property (nonatomic, copy, readonly, nonnull) NSString *name;
#property (nonatomic, readonly) double minValue;
#property (nonatomic, readonly) double maxValue;
#property (nonatomic) double value;
- (instancetype _Nonnull)initWithName:(NSString *_Nonnull)name minValue:(double)minValue maxValue:(double)maxValue initialValue:(double)value;
#end
#implementation Parameter
- (instancetype)initWithName:(NSString *)name minValue:(double)minValue maxValue:(double)maxValue initialValue:(double)value {
if (self = [super init]) {
_name = [name copy];
_minValue = minValue;
_maxValue = maxValue;
_value = value;
}
return self;
}
#end
#interface ParameterView: UIStackView
#property (nonatomic, strong, readonly, nonnull) Parameter *parameter;
#property (nonatomic, strong, readonly, nonnull) UILabel *nameLabel;
#property (nonatomic, strong, readonly, nonnull) UILabel *minValueLabel;
#property (nonatomic, strong, readonly, nonnull) UISlider *valueSlider;
#property (nonatomic, strong, readonly, nonnull) UILabel *maxValueLabel;
#property (nonatomic, strong, readonly, nonnull) UITextField *valueTextField;
- (instancetype _Nonnull)initWithParameter:(Parameter *_Nonnull)parameter;
#end
static void *kvoParameterValue = &kvoParameterValue;
#implementation ParameterView
- (instancetype)initWithParameter:(Parameter *)parameter {
if (self = [super init]) {
self.axis = UILayoutConstraintAxisHorizontal;
self.alignment = UIStackViewAlignmentCenter;
self.spacing = 2;
_parameter = parameter;
_nameLabel = [self pv_labelWithText:[parameter.name stringByAppendingString:#":"] alignment:NSTextAlignmentRight];
_minValueLabel = [self pv_labelWithText:[NSString stringWithFormat:#"%.0f", parameter.minValue] alignment:NSTextAlignmentRight];
_maxValueLabel = [self pv_labelWithText:[NSString stringWithFormat:#"%.0f", parameter.maxValue] alignment:NSTextAlignmentLeft];
_valueSlider = [[UISlider alloc] init];
_valueSlider.translatesAutoresizingMaskIntoConstraints = NO;
_valueSlider.minimumValue = parameter.minValue;
_valueSlider.maximumValue = parameter.maxValue;
_valueTextField = [[UITextField alloc] init];
_valueTextField.translatesAutoresizingMaskIntoConstraints = NO;
_valueTextField.borderStyle = UITextBorderStyleRoundedRect;
_valueTextField.text = [self stringWithValue:parameter.minValue];
CGFloat width = [_valueTextField systemLayoutSizeFittingSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].width;
_valueTextField.text = [self stringWithValue:parameter.maxValue];
width = MAX(width, [_valueTextField systemLayoutSizeFittingSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].width);
[_valueTextField.widthAnchor constraintGreaterThanOrEqualToConstant:width].active = YES;
[self addArrangedSubview:_nameLabel];
[self addArrangedSubview:_minValueLabel];
[self addArrangedSubview:_valueSlider];
[self addArrangedSubview:_maxValueLabel];
[self addArrangedSubview:_valueTextField];
[_parameter addObserver:self forKeyPath:#"value" options:0 context:kvoParameterValue];
[_valueSlider addTarget:self action:#selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
[self updateViews];
}
return self;
}
- (UILabel *)pv_labelWithText:(NSString *)text alignment:(NSTextAlignment)alignment {
UILabel *label = [[UILabel alloc] init];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.text = text;
label.textAlignment = alignment;
[label setContentCompressionResistancePriority:UILayoutPriorityRequired - 1 forAxis:UILayoutConstraintAxisHorizontal];
[label setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
return label;
}
- (void)constrainColumnsToReferenceView:(ParameterView *)referenceView {
[NSLayoutConstraint activateConstraints:#[
[_nameLabel.widthAnchor constraintEqualToAnchor:referenceView.nameLabel.widthAnchor],
[_minValueLabel.widthAnchor constraintEqualToAnchor:referenceView.minValueLabel.widthAnchor],
[_valueSlider.widthAnchor constraintEqualToAnchor:referenceView.valueSlider.widthAnchor],
[_maxValueLabel.widthAnchor constraintEqualToAnchor:referenceView.maxValueLabel.widthAnchor],
[_valueTextField.widthAnchor constraintEqualToAnchor:referenceView.valueTextField.widthAnchor],
]];
}
- (void)sliderValueChanged:(UISlider *)slider {
_parameter.value = slider.value;
}
- (void)updateViews {
_valueSlider.value = _parameter.value;
_valueTextField.text = [self stringWithValue:_parameter.value];
}
- (NSString *)stringWithValue:(double)value {
return [NSString stringWithFormat:#"%.4f", value];
}
- (void)dealloc {
[_parameter removeObserver:self forKeyPath:#"value" context:kvoParameterValue];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == kvoParameterValue) {
[self updateViews];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#end
#interface ViewController ()
#end
#implementation ViewController {
NSArray<Parameter *> *_parameters;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
if (self = [super initWithCoder:decoder]) {
_parameters = #[
[[Parameter alloc] initWithName:#"Rotation, deg" minValue:-180 maxValue:180 initialValue:61.9481],
[[Parameter alloc] initWithName:#"Field of view scale" minValue:0 maxValue:1 initialValue:0.7013],
[[Parameter alloc] initWithName:#"Fisheye lens distortion" minValue:0 maxValue:1 initialValue:0.3041],
[[Parameter alloc] initWithName:#"Tilt vertical" minValue:-90 maxValue:90 initialValue:42.6623],
[[Parameter alloc] initWithName:#"X" minValue:-1 maxValue:1 initialValue:0.6528],
[[Parameter alloc] initWithName:#"Y" minValue:-1 maxValue:1 initialValue:-0.3026],
[[Parameter alloc] initWithName:#"Z" minValue:-1 maxValue:1 initialValue:0],
];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
UIStackView *rootStack = [[UIStackView alloc] init];
rootStack.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:rootStack];
[NSLayoutConstraint activateConstraints:#[
[rootStack.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:8],
[rootStack.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:8],
[rootStack.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-8],
]];
rootStack.axis = UILayoutConstraintAxisVertical;
rootStack.spacing = 4;
rootStack.alignment = UIStackViewAlignmentFill;
ParameterView *firstParameterView;
for (Parameter *p in _parameters) {
ParameterView *pv = [[ParameterView alloc] initWithParameter:p];
[rootStack addArrangedSubview:pv];
if (firstParameterView == nil) {
firstParameterView = pv;
} else {
[pv constrainColumnsToReferenceView:firstParameterView];
}
}
}
#end
Related
I have 4 screens that have one almost the same view:
And one screen have the same view but with slightly different UI:
So, my question: Can I use one xib and adapt states (active, inactive) and change ui for different screen? How I can do it?
Here is an example of this kind of class
In Your .m file of custom XIB class if you are using objective-c.
- (void)foo:(NSString*)labelText andButtonText:(NSString*)buttonTitle {
//Do your code here for some screen like change labels and button text
}
- (void)bar:(NSString*)labelText andButtonText:(NSString*)buttonTitle {
//Do your code here for some another screen and change labels and button text
}
In Your .h file of custom XIB class if you are using objective-c.
- (void)foo:(NSString*)labelText andButtonText:(NSString*)buttonTitle;
- (void)bar:(NSString*)labelText andButtonText:(NSString*)buttonTitle;
In your view controller where you want to display the custom UI
Create an instance of your xib or add through interfacebuilder
And On your instance of custom class call the method as required.
Below is a class I've used in one of my project to get a clear understanding.
#import <UIKit/UIKit.h>
#import "DYRateView.h"
#interface LevelAndRankDetails : UIView
#property (nonatomic, strong) IBOutlet UIView* contentView;
#property (nonatomic, strong) IBOutlet UIView* viewContainer;
#property (nonatomic, strong) IBOutlet UILabel* lblLevel;
#property (nonatomic, strong) IBOutlet UILabel* lblRanking;
#property (weak, nonatomic) IBOutlet DYRateView *viewRate;
- (void)setLevel:(NSNumber*)level andRanking:(NSNumber*)ranking;
- (void)setupUI;
#end
.m File
#import "LevelAndRankDetails.h"
#import "AppDelegate.h"
#import "Constants.h"
#implementation LevelAndRankDetails
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]){
[self commonSetup];
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self commonSetup];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
}
- (void)viewFromNibForClass {
[[NSBundle mainBundle] loadNibNamed:[[self class] description] owner:self options:nil];
[self addSubview:self.contentView];
self.contentView.frame = self.bounds;
}
- (void)commonSetup {
[self viewFromNibForClass];
//For View's Corner Radius
self.contentView.layer.cornerRadius = 12;
self.contentView.layer.masksToBounds = YES;
self.contentView.backgroundColor = kDefaultBackgroundGreyColor;
self.viewContainer.backgroundColor = kDefaultBackgroundGreyColor;//[UIColor clearColor];
self.backgroundColor = kDefaultBackgroundGreyColor;
//self.viewContainer.backgroundColor = UIColorFromRGB(0xBB9657);//[kLearnFromLightColor colorWithAlphaComponent:0.5];
self.viewRate.rate = 0;
self.viewRate.editable = NO;
self.viewRate.delegate = nil;
self.viewRate.alignment = RateViewAlignmentCenter;
self.viewRate.backgroundColor = [UIColor clearColor];
[self.viewRate setEmptyStarImage:[UIImage imageNamed:#"StarEmpty"]];
UIImage* imageFullStar = [[UIImage imageNamed:#"StarFull"] imageTintedWithColor:kSliderDarkYellowColor];
[self.viewRate setFullStarImage:imageFullStar];
self.lblLevel.textColor = [UIColor whiteColor];
self.lblRanking.textColor = [UIColor whiteColor];
//For Teacher label
}
- (void)setupUI {
self.contentView.layer.cornerRadius = 0;
self.contentView.layer.masksToBounds = YES;
self.contentView.backgroundColor = [UIColor clearColor];
self.viewContainer.backgroundColor = [UIColor clearColor];//[UIColor clearColor];
self.backgroundColor = [UIColor clearColor];
}
- (void)setRanking:(CGFloat)ranking {
self.viewRate.rate = ranking;
}
- (void)setLevel:(NSNumber*)level {
self.lblLevel.text = [NSString stringWithFormat:#"Level : %#",level];
}
- (void)setLevel:(NSNumber*)level andRanking:(NSNumber*)ranking {
if (level.integerValue > 0) {
[self setLevel:level];
}
if (ranking.doubleValue > 0) {
CGFloat rankingConverted = ranking.floatValue;
[self setRanking:rankingConverted];
}
}
#end
And this is how you use it in your view controller
LevelAndRankDetails* toolTipCustomView = [[LevelAndRankDetails alloc] initWithFrame:CGRectMake(0, 0, 250, 66)];
toolTipCustomView.backgroundColor = [UIColor blackColor];
[toolTipCustomView setLevel:#(10) andRanking:#(3)];
Our iPhone app currently supports IOS 8/9/10. I am having difficulty supporting voice over accessibility for a custom UITableViewCell. I have gone through the following SO posts, but none of the suggestions have worked. I want individual components to be accessible.
Custom UITableview cell accessibility not working correctly
Custom UITableViewCell trouble with UIAccessibility elements
Accessibility in custom drawn UITableViewCell
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html#//apple_ref/doc/uid/TP40008785-CH102-SW10
http://useyourloaf.com/blog/voiceover-accessibility/
Unfortunately for me, the cell is not detected by the accessibility inspector. Is there a way to voice over accessibility to pick up individual elements within the table view cell? When debugging this issue on both device and a simulator, I found that the XCode calls isAccessibleElement function. When the function returns NO, then the rest of the methods are skipped. I am testing on IOS 9.3 in XCode.
My custom table view cell consists of a label and a switch as shown below.
The label is added to the content view, while the switch is added to a custom accessory view.
The interface definition is given below
#interface MyCustomTableViewCell : UITableViewCell
///Designated initializer
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier;
///Property that determines if the switch displayed in the cell is ON or OFF.
#property (nonatomic, assign) BOOL switchIsOn;
///The label to be displayed for the alert
#property (nonatomic, strong) UILabel *alertLabel;
#property (nonatomic, strong) UISwitch *switch;
#pragma mark - Accessibility
// Used for setting up accessibility values. This is used to generate accessibility labels of
// individual elements.
#property (nonatomic, strong) NSString* accessibilityPrefix;
-(void)setAlertHTMLText:(NSString*)title;
#end
The implementation block is given below
#interface MyCustomTableViewCell()
#property (nonatomic, strong) UIView *customAccessoryView;
#property (nonatomic, strong) NSString *alertTextString;
#property (nonatomic, strong) NSMutableArray* accessibleElements;
#end
#implementation MyCustomTableViewCell
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
{
if(self = [super initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:reuseIdentifier]) {
[self configureTableCell];
}
return self;
}
- (void)configureTableCell
{
if (!_accessibleElements) {
_accessibleElements = [[NSMutableArray alloc] init];
}
//Alert label
self.alertLabel = [[self class] makeAlertLabel];
[self.contentView setIsAccessibilityElement:YES];
//
[self.contentView addSubview:self.alertLabel];
// Custom AccessoryView for easy styling.
self.customAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
[self.customAccessoryView setIsAccessibilityElement:YES];
[self.contentView addSubview:self.customAccessoryView];
//switch
self.switch = [[BAUISwitch alloc] initWithFrame:CGRectZero];
[self.switch addTarget:self action:#selector(switchWasFlipped:) forControlEvents:UIControlEventValueChanged];
[self.switch setIsAccessibilityElement:YES];
[self.switch setAccessibilityTraits:UIAccessibilityTraitButton];
[self.switch setAccessibilityLabel:#""];
[self.switch setAccessibilityHint:#""];
self.switch.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
[self.customAccessoryView addSubview:self.switch];
}
+ (UILabel *)makeAlertLabel
{
UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectZero];
alertLabel.backgroundColor = [UIColor clearColor];
alertLabel.HTMLText = #"";
alertLabel.numberOfLines = 0;
alertLabel.lineBreakMode = LINE_BREAK_WORD_WRAP
[alertLabel setIsAccessibilityElement:YES];
return alertLabel;
}
-(void)setAlertHTMLText:(NSString*)title{
_alertTextString = [NSString stringWithString:title];
[self.alertLabel setText:_alertTextString];
}
- (BOOL)isAccessibilityElement {
return NO;
}
// The view encapsulates the following elements for the purposes of
// accessibility.
-(NSArray*) accessibleElements {
if (_accessibleElements && [_accessibleElements count] > 0) {
[_accessibleElements removeAllObjects];
}
// Fetch a new copy as the values may have changed.
_accessibleElements = [[NSMutableArray alloc] init];
UIAccessibilityElement* alertLabelElement =
[[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
//alertLabelElement.accessibilityFrame = [self convertRect:self.contentView.frame toView:nil];
alertLabelElement.accessibilityLabel = _alertTextString;
alertLabelElement.accessibilityTraits = UIAccessibilityTraitStaticText;
[_accessibleElements addObject:alertLabelElement];
UIAccessibilityElement* switchElement =
[[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
// switchElement.accessibilityFrame = [self convertRect:self.customAccessoryView.frame toView:nil];
switchElement.accessibilityTraits = UIAccessibilityTraitButton;
// If you want custom values, just override it in the invoking function.
NSMutableString* accessibilityString =
[NSMutableString stringWithString:self.accessibilityPrefix];
[accessibilityString appendString:#" Switch "];
if (self.switchh.isOn) {
[accessibilityString appendString:#"On"];
} else {
[accessibilityString appendString:#"Off"];
}
switchElement.accessibilityLabel = [accessibilityString copy];
[_accessibleElements addObject:switchElement];
}
return _accessibleElements;
}
// In case accessibleElements is not initialized.
- (void) initializeAccessibleElements {
_accessibleElements = [self accessibleElements];
}
- (NSInteger)accessibilityElementCount
{
return [_accessibleElements count]
}
- (id)accessibilityElementAtIndex:(NSInteger)index
{
[self initializeAccessibleElements];
return [_accessibleElements objectAtIndex:index];
}
- (NSInteger)indexOfAccessibilityElement:(id)element
{
[self initializeAccessibleElements];
return [_accessibleElements indexOfObject:element];
}
#end
First of all, from the pattern you described, I'm not sure why you would want to differentiate between different elements in a cell. Generally, Apple keeps every cell a single accessibility element. A great place to see the expected iOS VO behavior for cells with labels and switches is in Settings App.
If you still believe the best way to handle your cells is to make them contain individual elements, then that is actually the default behavior of a cell when the UITableViewCell itself does not have an accessibility label. So, I've modified your code below and run it on my iOS device (running 9.3) and it works as you described you would like.
You'll notice a few things.
I deleted all the custom accessibilityElements code. It is not necessary.
I deleted the override of isAccessibilityElement on the UITableViewCell subclass itself. We want default behavior.
I commented out setting the content view as an accessibilityElement -- we want that to be NO so that the tree-builder looks inside of it for elements.
I set customAccessoryView's isAccessibilityElement to NO as well for the same reason as above. Generally, NO says "keep looking down the tree" and YES says "stop here, this is my leaf as far as accessibility is concerned."
I hope this is helpful. Once again, I do really encourage you to mimic Apple's VO patterns when designing for Accessibility. I think it's awesome that you're making sure your app is accessible!
#import "MyCustomTableViewCell.h"
#interface MyCustomTableViewCell()
#property (nonatomic, strong) UIView *customAccessoryView;
#property (nonatomic, strong) NSString *alertTextString;
#property (nonatomic, strong) NSMutableArray* accessibleElements;
#end
#implementation MyCustomTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if(self = [super initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:reuseIdentifier]) {
[self configureTableCell];
}
return self;
}
// just added this here to get the cell to lay out for myself
- (void)layoutSubviews {
[super layoutSubviews];
const CGFloat margin = 8;
CGRect b = self.bounds;
CGSize labelSize = [self.alertLabel sizeThatFits:b.size];
CGFloat maxX = CGRectGetMaxX(b);
self.alertLabel.frame = CGRectMake(margin, margin, labelSize.width, labelSize.height);
CGSize switchSize = [self.mySwitch sizeThatFits:b.size];
self.customAccessoryView.frame = CGRectMake(maxX - switchSize.width - margin * 2, b.origin.y + margin, switchSize.width + margin * 2, switchSize.height);
self.mySwitch.frame = CGRectMake(margin, 0, switchSize.width, switchSize.height);
}
- (void)configureTableCell
{
//Alert label
self.alertLabel = [[self class] makeAlertLabel];
//[self.contentView setIsAccessibilityElement:YES];
//
[self.contentView addSubview:self.alertLabel];
// Custom AccessoryView for easy styling.
self.customAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
[self.customAccessoryView setIsAccessibilityElement:NO]; // Setting this to NO tells the the hierarchy builder to look inside
[self.contentView addSubview:self.customAccessoryView];
self.customAccessoryView.backgroundColor = [UIColor purpleColor];
//switch
self.mySwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
//[self.mySwitch addTarget:self action:#selector(switchWasFlipped:) forControlEvents:UIControlEventValueChanged];
[self.mySwitch setIsAccessibilityElement:YES]; // This is default behavior
[self.mySwitch setAccessibilityTraits:UIAccessibilityTraitButton]; // No tsure why this is here
[self.mySwitch setAccessibilityLabel:#"my swich"];
[self.mySwitch setAccessibilityHint:#"Tap to do something."];
self.mySwitch.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
[self.customAccessoryView addSubview:self.mySwitch];
}
+ (UILabel *)makeAlertLabel
{
UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectZero];
alertLabel.backgroundColor = [UIColor clearColor];
alertLabel.text = #"";
alertLabel.numberOfLines = 0;
[alertLabel setIsAccessibilityElement:YES];
return alertLabel;
}
-(void)setAlertHTMLText:(NSString*)title{
_alertTextString = [NSString stringWithString:title];
[self.alertLabel setText:_alertTextString];
}
#end
first question so take it easy!
Creating a simple iOS App. I have a view which contains a rotating dial (a UIImageView) (rotates through Rotation Gesture) and a UIButton which when pressed uses UIView animateWithDuration to move the button to one of 3 locations.
My issue.. rotating the dial is fine, moving the button is fine, BUT.. when I move the button, then rotate the dial, the location of the button is "reset" to its original frame.
How can I stop the rotation effecting the location of the button / the movement of the button.
ViewController.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface ViewController : UIViewController <UIGestureRecognizerDelegate>{
IBOutlet UIImageView *lock;
IBOutlet UILabel *number;
IBOutlet UILabel *displayNumber1;
IBOutlet UILabel *displayNumber2;
IBOutlet UILabel *displayNumber3;
IBOutlet UIButton *slider;
AVAudioPlayer *theAudio;
}
#property (retain, nonatomic) IBOutlet UIImageView *lock;
#property (retain, nonatomic) IBOutlet UILabel *number;
#property (retain, nonatomic) IBOutlet UILabel *displayNumber1;
#property (retain, nonatomic) IBOutlet UILabel *displayNumber2;
#property (retain, nonatomic) IBOutlet UILabel *displayNumber3;
#property (retain, nonatomic) IBOutlet UIButton *slider;
#property (retain, nonatomic) AVAudioPlayer *theAudio;
-(IBAction)moveSlider:(id)sender;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI))
#synthesize lock, number, slider, displayNumber1, displayNumber2, displayNumber3, theAudio;
CGFloat _lastRotation;
int lastNumber;
NSTimer *clickTimer;
int _whichNumberSelected;
int _currentDirection;
CGFloat _changeDirectionMatch;
float no1Left = 61;
float no2Left = 151;
float no3Left = 238;
NSString *lastText1;
CGRect newFrameSet;
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (void)viewDidLoad {
[super viewDidLoad];
lock.userInteractionEnabled = YES;
UIRotationGestureRecognizer *rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(handleRotationWithGestureRecognizer:)];
rotationGestureRecognizer.delegate = self;
[lock addGestureRecognizer:rotationGestureRecognizer];
_lastRotation = 0;
lastNumber = 0;
NSString *path = [[NSBundle mainBundle] pathForResource:#"click" ofType:#"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: path];
theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:NULL];
theAudio.volume = 1.0;
[theAudio prepareToPlay];
_currentDirection = 0;
_changeDirectionMatch = 0;
_whichNumberSelected = 1;
lastText1 = ([NSString stringWithFormat:#"0"]);
}
-(IBAction)moveSlider:(id)sender {
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
CGRect newFrame = slider.frame;
switch (_whichNumberSelected) {
case 1:
newFrame.origin.x= no2Left;
_whichNumberSelected = 2;
break;
case 2:
newFrame.origin.x= no3Left;
_whichNumberSelected = 3;
break;
case 3:
newFrame.origin.x= no2Left;
_whichNumberSelected = 4;
break;
case 4:
newFrame.origin.x= no1Left;
_whichNumberSelected = 1;
break;
default:
break;
}
slider.frame = newFrame;
newFrameSet = newFrame;
} completion:nil];
}
-(void)handleRotationWithGestureRecognizer:(UIRotationGestureRecognizer *)rotationGestureRecognizer{
CGFloat newRot = RADIANS_TO_DEGREES(rotationGestureRecognizer.rotation) / 3.6;
lock.transform = CGAffineTransformRotate(lock.transform, rotationGestureRecognizer.rotation);
CGFloat c = _lastRotation - newRot;
_lastRotation = c;
if(c < 0){
c = c + 100;
_lastRotation = c;
}
if(c >= 100){
c = c-100;
_lastRotation = c;
}
if(c >= 99.5){
displayNumber1.text = #"0";
} else {
displayNumber1.text = ([NSString stringWithFormat:#"%.f", c]);
}
if([displayNumber1.text isEqualToString:lastText1]){
} else {
[theAudio play];
}
lastText1 = displayNumber1.text;
rotationGestureRecognizer.rotation = 0.0;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Welcome to Stack Overflow.
My guess is that your view controller has AutoLayout active. If that's the case, you need to change your animation code so that you animate your button by manipulating one or more constraints on it rather than changing it's frame.
The problem is that when you change the frame directly, at some point something triggers a re-layout of the form, and the constraints snap it back into place. Even if you don't explicitly add constraints, the system does.
The rotation animation should be fine since that is changing the rotation on the view's transform, not it's position, and there are no constraints I'm aware of for rotation.
What you do to animate using constraints is to attach constraints to the button in IB, then control-drag from the constraints to your view controller's header to create outlets to the constraints. (Leading and top edge constraints, say.)
Then in your animation change the constant(s) on the constraint(s) and call layoutIfNeeded.
The alternative is to turn off auto-layout for your view controller, use old style "struts and springs" layout, and then your frame animation will work as always.
I am using GitHub project https://github.com/mayuur/MJParallaxCollectionView
I am trying to add a UIView and UILabel to the cells being displayed. I have tried so many solutions it would probably just be easier to ask someone how to do it.
So with that can someone add a UIView and UILabel to the UICollectionView displaying some text? This can be done programmatically or via storyboard, whichever suits your style.
I tried adding related logic in MJCollectionViewCell.m setupImageView method. Also, tried MJRootViewController cellForItemAtIndexPath method. But I still can't get the UIView and UILabel to display over the UIImage object in MJCollectionViewCell.
#property (nonatomic, strong, readwrite) UIImage *image;
MJCollectionViewCell.h
//
// MJCollectionViewCell.h
// RCCPeakableImageSample
//
// Created by Mayur on 4/1/14.
// Copyright (c) 2014 RCCBox. All rights reserved.
//
#import <UIKit/UIKit.h>
#define IMAGE_HEIGHT 200
#define IMAGE_OFFSET_SPEED 25
#interface MJCollectionViewCell : UICollectionViewCell
/*
image used in the cell which will be having the parallax effect
*/
#property (nonatomic, strong, readwrite) UIImage *image;
/*
Image will always animate according to the imageOffset provided. Higher the value means higher offset for the image
*/
#property (nonatomic, assign, readwrite) CGPoint imageOffset;
//#property (nonatomic,readwrite) UILabel *textLabel;
#property (weak, nonatomic) IBOutlet UILabel *textLabel;
#property (weak, nonatomic) IBOutlet UIImageView *cellImage;
#property (nonatomic,readwrite) NSString *text;
#property(nonatomic,readwrite) CGFloat x,y,width,height;
#property (nonatomic,readwrite) NSInteger lineSpacing;
#property (nonatomic, strong) IBOutlet UIView* overlayView;
#end
MJCollectionViewCell.m
// MJCollectionViewCell.m
// RCCPeakableImageSample
//
// Created by Mayur on 4/1/14.
// Copyright (c) 2014 RCCBox. All rights reserved.
//
#import "MJCollectionViewCell.h"
#interface MJCollectionViewCell()
#property (nonatomic, strong, readwrite) UIImageView *MJImageView;
#end
#implementation MJCollectionViewCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) [self setupImageView];
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) [self setupImageView];
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#pragma mark - Setup Method
- (void)setupImageView
{
// Clip subviews
self.clipsToBounds = YES;
/*
// Add image subview
self.MJImageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, IMAGE_HEIGHT)];
self.MJImageView.backgroundColor = [UIColor redColor];
self.MJImageView.contentMode = UIViewContentModeScaleAspectFill;
self.MJImageView.clipsToBounds = NO;
[self addSubview:self.MJImageView];
*/
//New Code in method
// Add image subview
self.MJImageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, IMAGE_HEIGHT)];
self.MJImageView.backgroundColor = [UIColor redColor];
self.MJImageView.contentMode = UIViewContentModeScaleAspectFill;
self.MJImageView.clipsToBounds = NO;
//self.overlayView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.4f];
// UIView *anotherView = [[UIView alloc]initWithFrame:CGRectMake(0.0, 0.0, 20.0, 20.0)];
// UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0.0, 0.0, 50.0, 20.0)];
// label.text = #"Hello";
// [anotherView addSubview:label];
[self addSubview:self.MJImageView];
[self addSubview:self.overlayView];
[self addSubview:self.textLabel];
}
# pragma mark - Setters
- (void)setImage:(UIImage *)image
{
// Store image
self.MJImageView.image = image;
// Update padding
[self setImageOffset:self.imageOffset];
}
- (void)setImageOffset:(CGPoint)imageOffset
{
// Store padding value
_imageOffset = imageOffset;
// Grow image view
CGRect frame = self.MJImageView.bounds;
CGRect offsetFrame = CGRectOffset(frame, _imageOffset.x, _imageOffset.y);
self.MJImageView.frame = offsetFrame;
}
//This was added from MPSkewed may need to remove if not called.
- (void)setText:(NSString *)text{
_text=text;
if (!self.textLabel) {
CGFloat realH=self.height*2/3-self.lineSpacing;
CGFloat latoA=realH/3;
// self.textLabel=[[UILabel alloc] initWithFrame:CGRectMake(10,latoA/2, self.width-20, realH)];
self.textLabel.layer.anchorPoint=CGPointMake(.5, .5);
self.textLabel.font=[UIFont fontWithName:#"HelveticaNeue-ultralight" size:38];
self.textLabel.numberOfLines=3;
self.textLabel.textColor=[UIColor whiteColor];
self.textLabel.shadowColor=[UIColor blackColor];
self.textLabel.shadowOffset=CGSizeMake(1, 1);
self.textLabel.transform=CGAffineTransformMakeRotation(-(asin(latoA/(sqrt(self.width*self.width+latoA*latoA)))));
[self addSubview:self.textLabel];
}
self.textLabel.text=text;
}
#end
In the .h of the cell:
#property (strong, nonatomic, readonly) UILabel *label
In the .m of the cell:
#property (strong, nonatomic, readwrite) UILabel *label
In the setupImageView method (which you should probably rename to be more generic), after the image view is created, something like this:
self.label = [[UILabel alloc] initWithFrame:someFrame]; // or CGRectZero if you are using auto layout
self.label.textColor = ...; // any other setup you want to do
[self addSubview:self.label];
self.label.frame = ...; // or set it up using auto layout
And in your data source’s -collectionView:cellForItemAtIndexPath:
cell.label.text = #"some text";
That is a simple approach. In practice, I would probably expose a property for the string only, not the whole label, but that depends on what you are doing and how much control you need.
I'm struggling with a retaining issue between two of my UIViewControllers. The view controllers are never deleted causing my app memory to keep growing memory consumption.
UITitleScreenViewController is my initial view controller. When I go from it to UIChooseAntViewController (a choose player screen) I want to relinquish ownership of UITitleViewController but as you can see in the instruments below the controller is still retained after the transition:
The second image is the retain/release history. All entries prior to #133 were issued on the app startup. I believe #133 and #140 are pairs created by the storyboard segue. So whose responsibility is to issue that extra release to destroy the controller? I tried to set self.view = nil on my willDidDisappear method but no deal.
Not only it is not releasing the controllers but it is creating new instances of them each time a transition. For instance, when I come back from ChooseAnt to Title it creates another instance of UITitleViewController!
Things that are important to say:
1) NSZombies flag is not ticked in the target scheme
2) There are no blocks in my UITitleViewController, and I commented out all blocks in UIChooseAntController. In fact these controllers are very simple. UITitle is entirely defined via storyboard (just a view with a background and two buttons performing segues)
while UIChooseAnt is a control that presents a background and a swipe interface to display available characters and radio buttons. The segue is performed programatically by calling [self performSegueWithIdentifier];
3) I don't know if this matters but the segues are defined as modal and have no animation.
EDIT: 4) None of the the controllers reference each other.
Below is the source code for the TitleViewController
This problem is driving me crazy. If anyone could shed some light on it. Anything would be of great help! Thanks!
#interface SMTitleScreenViewController ()
#property (weak, nonatomic) IBOutlet UIButton *buttonPlay;
#property (weak, nonatomic) IBOutlet UIButton *buttonCamera;
- (IBAction)onButtonPlay:(id)sender;
- (IBAction)onButtonCamera:(id)sender;
#end
#implementation SMTitleScreenViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIColor* color = [UIColor colorWithRed:0.2509f green:0.1176f blue:0.0745f alpha:1.0f];
UIFont* font = [UIFont fontWithName:#"Jungle Roar" size:BUTTON_FONT_SIZE];
NSString* playString = NSLocalizedString(#"Play", #"");
NSString* cameraString = NSLocalizedString(#"Camera", #"");
[self.buttonPlay setTitle:playString forState:UIControlStateNormal];
[self.buttonPlay setTitle:playString forState:UIControlStateHighlighted];
[self.buttonPlay setTitleColor:color forState:UIControlStateNormal];
[self.buttonPlay setTitleColor:color forState:UIControlStateHighlighted];
self.buttonPlay.titleLabel.font = font;
[self.buttonCamera setTitle:cameraString forState:UIControlStateNormal];
[self.buttonCamera setTitle:cameraString forState:UIControlStateHighlighted];
[self.buttonCamera setTitleColor:color forState:UIControlStateNormal];
[self.buttonCamera setTitleColor:color forState:UIControlStateHighlighted];
self.buttonCamera.titleLabel.font = font;
}
- (void) viewDidDisappear:(BOOL)animated
{
if ([self.view window] == nil)
{
self.view = nil;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if ([self.view window] == nil)
{
self.view = nil;
}
}
- (IBAction)onButtonPlay:(id)sender
{
}
- (IBAction)onButtonCamera:(id)sender
{
}
EDIT: UIChooseAntViewController (as requested)
#interface SMChooseAntViewController ()
#property (strong, nonatomic) UIImageView* rope;
#property (strong, nonatomic) UIImageView* antFrontLayer;
#property (strong, nonatomic) UIImageView* antBackLayer;
#property (strong, nonatomic) NSArray* antFrontImages;
#property (strong, nonatomic) NSArray* antBackImages;
#property (strong, nonatomic) NSArray* antNameImages;
#property (strong, nonatomic) UIButton* leftButton;
#property (strong, nonatomic) UIButton* rightButton;
#property (strong, nonatomic) UIButton* confirmButton;
#property (nonatomic) NSUInteger selectedAntID;
#property (strong, nonatomic) UIImage* radioImageHighlighted;
#property (strong, nonatomic) UIImage* radioImage;
#property (strong, nonatomic) NSMutableArray* radioViews;
#property (weak, nonatomic) IBOutlet UILabel *antDescriptionLabel;
#property (weak, nonatomic) IBOutlet UIImageView *antDescriptionBG;
#property (strong, nonatomic) UIImageView* antNameView;
#property (strong, nonatomic) UISwipeGestureRecognizer* leftSwipeRecognizer;
#property (strong, nonatomic) UISwipeGestureRecognizer* rightSwipeRecognizer;
- (void) onArrowButton:(id)sender;
- (void) onConfirmButton:(id)sender;
- (void) respondToSwipe:(UISwipeGestureRecognizer*)recognizer;
#end
#implementation SMChooseAntViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
// Needed to come in between front and back player image layers
UIImage* ropeImage = [UIImage imageNamed:ROPE_IMAGE_PATH];
self.rope = [[UIImageView alloc] initWithImage:ropeImage];
self.rope.center = CGPointMake(screenSize.width / 2.0f, ropeImage.size.height / 2.0f);
UIColor* brownColor = [UIColor colorWithRed:0.2509f green:0.1176f blue:0.0745f alpha:1.0f];
self.antDescriptionLabel.textColor = brownColor;
self.antDescriptionLabel.numberOfLines = 0;
NSArray* antNames = [SMProfile antNames];
// Cache available Player Views in a NSArray
UIImage* frontImages[MAX_AVAILABLE_ANTS];
UIImage* backImages[MAX_AVAILABLE_ANTS];
UIImage* nameImages[MAX_AVAILABLE_ANTS];
for (NSUInteger i = 0; i < MAX_AVAILABLE_ANTS; ++i)
{
NSString* antName = [antNames objectAtIndex:i];
frontImages[i] = [SMImage imageNamed:[NSString stringWithFormat:#"%#_title_front.png", antName]];
backImages[i] = [SMImage imageNamed:[NSString stringWithFormat:#"%#_title_back.png", antName]];
nameImages[i] = [SMImage imageNamed:[NSString stringWithFormat:#"%#_name.png", antName]];
}
self.antFrontImages = [NSArray arrayWithObjects:frontImages[0], frontImages[1], frontImages[2], nil];
self.antBackImages = [NSArray arrayWithObjects:backImages[0], backImages[1], backImages[2], nil];
self.antNameImages = [NSArray arrayWithObjects:nameImages[0], nameImages[1], nameImages[2], nil];
// Load Selected player from profile
SMProfile* profile = [SMProfile mainProfile];
self.selectedAntID = profile.antID.unsignedIntegerValue;
self.antFrontLayer = [[UIImageView alloc] initWithImage:[self.antFrontImages objectAtIndex:self.selectedAntID]];
self.antBackLayer = [[UIImageView alloc] initWithImage:[self.antBackImages objectAtIndex:self.selectedAntID]];
self.antNameView = [[UIImageView alloc] initWithImage:[self.antNameImages objectAtIndex:self.selectedAntID]];
self.antNameView.center = CGPointMake(screenSize.width / 2.0f, self.antDescriptionBG.frame.origin.y);
NSString* antDescriptionKey = [NSString stringWithFormat:#"AntDescription%lu", (unsigned long)self.selectedAntID];
self.antDescriptionLabel.text = NSLocalizedString(antDescriptionKey, #"");
self.antDescriptionLabel.numberOfLines = 0;
self.antDescriptionLabel.adjustsFontSizeToFitWidth = YES;
self.antFrontLayer.center = CGPointMake(screenSize.width / 2.0f, ropeImage.size.height * 0.75f);
self.antBackLayer.center = self.antFrontLayer.center;
// Here a perform button creation, loading and positioning
// No blocks are being called
// add Target to buttons
[self.leftButton addTarget:self action:#selector(onArrowButton:) forControlEvents:UIControlEventTouchUpInside];
[self.rightButton addTarget:self action:#selector(onArrowButton:) forControlEvents:UIControlEventTouchUpInside];
[self.confirmButton addTarget:self action:#selector(onConfirmButton:) forControlEvents:UIControlEventTouchUpInside];
// Create and configure SwipeRecognizers
self.leftSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(respondToSwipe:)];
self.leftSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:self.leftSwipeRecognizer];
self.rightSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(respondToSwipe:)];
self.rightSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:self.rightSwipeRecognizer];
// Here a create a custom page control scheme. I load two radio button images
// create views and add them to the root view node.
// Add remaining view to the hierarchy
[self.view addSubview:self.antBackLayer];
[self.view addSubview:self.rope];
[self.view addSubview:self.antFrontLayer];
[self.view addSubview:self.confirmButton];
[self.view bringSubviewToFront:self.antDescriptionBG];
[self.view bringSubviewToFront:self.antDescriptionLabel];
[self.view addSubview:self.leftButton];
[self.view addSubview:self.rightButton];
[self.view addSubview:self.antNameView];
[self.view bringSubviewToFront:[self.radioViews objectAtIndex:0]];
}
- (void) viewDidDisappear:(BOOL)animated
{
if ([self.view window] == nil)
{
self.rope = nil;
self.antFrontLayer = nil;
self.antBackLayer = nil;
self.antFrontImages = nil;
self.antBackImages = nil;
self.antNameImages = nil;
self.leftButton = nil;
self.rightButton = nil;
self.confirmButton = nil;
self.radioImageHighlighted = nil;
self.radioImage = nil;
self.radioViews = nil;
self.antNameView = nil;
self.leftSwipeRecognizer = nil;
self.rightSwipeRecognizer = nil;
self.view = nil;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if ([self.view window] == nil)
{
self.view = nil;
}
}
- (void)onArrowButton:(id)sender
{
UIButton* button = (UIButton*)sender;
NSInteger direction = button.tag;
// if on boundaries do nothing (first ant selected and swipe left or last ant selected and swipe right)
if ((self.selectedAntID == 0 && direction == -1) || (self.selectedAntID == (MAX_AVAILABLE_ANTS - 1) && direction == 1))
{
return;
}
// Update Radio Buttons. Unselect previous and select next.
UIImageView* currRadio = [self.radioViews objectAtIndex:self.selectedAntID];
currRadio.image = self.radioImage;
self.selectedAntID = (self.selectedAntID + MAX_AVAILABLE_ANTS + direction) % MAX_AVAILABLE_ANTS;
UIImageView* nextRadio = [self.radioViews objectAtIndex:self.selectedAntID];
nextRadio.image = self.radioImageHighlighted;
self.antFrontLayer.image = [self.antFrontImages objectAtIndex:self.selectedAntID];
self.antBackLayer.image = [self.antBackImages objectAtIndex:self.selectedAntID];
self.antNameView.image = [self.antNameImages objectAtIndex:self.selectedAntID];
// here I was issuing some block to perform the swipe animation for the ant image views. I commented them and I'm just replacing the images now (3 lines above)
}
- (void)onConfirmButton:(id)sender
{
// Save player choice to profile and perform segue
SMProfile* profile = [SMProfile mainProfile];
profile.antID = [NSNumber numberWithUnsignedInt:self.selectedAntID];
[profile save];
[self performSegueWithIdentifier:#"chooseAntToStageSelect" sender:self];
}
- (void) respondToSwipe:(UISwipeGestureRecognizer *)recognizer
{
// forward swipe to onArrowButton message
if (recognizer.direction == UISwipeGestureRecognizerDirectionLeft)
{
[self onArrowButton:self.rightButton];
}
else if (recognizer.direction == UISwipeGestureRecognizerDirectionRight)
{
[self onArrowButton:self.leftButton];
}
}
#end
When presenting B view controller from A, A will not release as A is the presentingViewController (please refer to the sdk doc).
Or if A,B are sub view controller of a navigation controller, A is store int he push stack which is not removed when pushing to B.
You are pushing a view controller on to a stack hence until the last one is not popped, the controller will not be released.
To go deep into dependencies on childs read the the article below.
Greatly explained, i am sure it'll help. :)
http://www.cocoawithlove.com/2009/07/rules-to-avoid-retain-cycles.html