Hiding View and other objects when button selected - ios

I'm trying to hide a UIView and objects within it when a button is deselected. The button is located in a TableView section header contained in the same ViewController.
The button press is being stored in...
Activity.h
#property BOOL invitesAll;
Activity.m
#import "Activity.h"
#dynamic invitesAll;
And the rest of the code...
InviteContactsViewController.h
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *expirationViewHeightConstraint;
#property (weak, nonatomic) IBOutlet UILabel *timeToRespondLabel;
#property (weak, nonatomic) IBOutlet UISlider *expirationSlider;
#property (weak, nonatomic) IBOutlet UILabel *expirationLabel;
#property (strong, nonatomic) IBOutlet UIButton *inviteAll;
InviteesTableViewController.h
#import "InviteContactsViewController.h"
#property (weak, nonatomic) NSLayoutConstraint *expirationViewHeightConstraint;
#property (weak, nonatomic) UILabel *timeToRespondLabel;
#property (weak, nonatomic) UISlider *expirationSlider;
#property (weak, nonatomic) UILabel *expirationLabel;
InviteesTableViewController.m
#import "InviteesTableViewController.h"
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
// create button
InviteAllButton *inviteAllButton = [InviteAllButton buttonWithType:UIButtonTypeCustom];
[inviteAllButton setTitle:#"Order Matters" forState:UIControlStateSelected];
[inviteAllButton setTitle:#"Order Doesn't Matter" forState:UIControlStateNormal];
inviteAllButton.titleLabel.font = [UIFont fontWithName:#"Avenir" size:11.0];
inviteAllButton.titleLabel.numberOfLines = 2;
[inviteAllButton addTarget:self action:#selector(inviteAllAction:) forControlEvents:UIControlEventTouchUpInside];
[headerView addSubview:inviteAllButton];
// set inviteAll button state
inviteAllButton.selected = !_activity.invitesAll;
// set constraints
[inviteAllButton setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *views = NSDictionaryOfVariableBindings(inviteAllButton, titleLabel);
[headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[titleLabel]-(>=5)-[inviteAllButton(96)]-5-|" options:0 metrics:nil views:views]];
[headerView addConstraint:[NSLayoutConstraint constraintWithItem:inviteAllButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:headerView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
}
- (void)inviteAllAction:(UIButton *)sender {
sender.selected = !sender.selected;
_activity.invitesAll = !_activity.invitesAll;
[self validateReordering];
[self.tableView reloadData];
NSString *state = _activity.invitesAll ? #"invitesAll" : #"orderMatters";
// In my attempt to hide the view, I've set an expirationViewHeightConstraint
// and try to change its constant as the button is pressed
if (self.activity.invitesAll) {
self.expirationViewHeightConstraint.constant = 0.f;
self.timeToRespondLabel.hidden = YES;
self.expirationSlider.hidden = YES;
self.expirationLabel.hidden = YES;
} else {
self.expirationViewHeightConstraint.constant = 35;
self.timeToRespondLabel.hidden = NO;
self.expirationSlider.hidden = NO;
self.expirationLabel.hidden = NO;
}
}
I referenced Max's answer on changing the height constraint constant to 0.f and tried to follow Simon's answer on accessing properties across ViewControllers as a basis, but to no avail. When I run this nothing visible happens with the expirationViewHeightConstraint, timeToRespondLabel, expirationSlider, or expirationLabel IBOutlets when inviteAll is pressed.
I'm a newb so guessing I'm missing something basic here. Is my approach flawed?

What you're doing with the property declarations in InviteesTableViewController is wrong. You can't just declare some properties in the child controller and expect that they will point to objects in some other controller (parent or not). You need to get a reference to the parent, which you can using self.parentViewController, and then access its properties with that,
InviteContactsViewController *inviteVC = (InviteContactsViewController *)self.parentViewController;
if (self.activity.invitesAll) {
inviteVC.expirationViewHeightConstraint.constant = 0.f;
inviteVC.timeToRespondLabel.hidden = YES;
inviteVC.expirationSlider.hidden = YES;
inviteVC.expirationLabel.hidden = YES;
} else {
inviteVC.expirationViewHeightConstraint.constant = 35;
inviteVC.timeToRespondLabel.hidden = NO;
inviteVC.expirationSlider.hidden = NO;
inviteVC.expirationLabel.hidden = NO;
}
You should delete all those property declarations in InviteesTableViewController.

Related

UIStackView with Slider, textField and Auto layout

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

When using JSQMessagesViewController as detailView in UISplitviewCOntroller, KeyBoardToolBar needs to appear in DetailViewController only

When using JSQMessagesViewController as detailView in UISplitViewController, KeyBoardToolBar needs to appear in DetailViewController only
late answer...
if u want to reduce the inputToolbar you need to create a subclass of JSQMessagesToolbarContentView and provide your own view for the tool bar's content view.
below i am giving the sample example, create a subcalss of JSQMessagesToolbarContentView name it as JSQMessagesToolbarContentView_custom in the subcalss add below code,
#import "JSQMessagesToolbarContentView.h"
#interface JSQMessagesToolbarContentView_custom : JSQMessagesToolbarContentView
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *holderViewLeadingConstraint;
#end
and in JSQMessagesToolbarContentView_custom.m file,
#import "UIView+JSQMessages.h"
#import "JSQMessagesToolbarContentView_custom.h"
#implementation JSQMessagesToolbarContentView_custom
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass([JSQMessagesToolbarContentView_custom class])
bundle:[NSBundle bundleForClass:[JSQMessagesToolbarContentView_custom class]]];
}
#pragma mark - Initialization
- (void)awakeFromNib
{
[super awakeFromNib];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.backgroundColor = [UIColor clearColor];
}
//below method will place the contentview to desired position
- (void)layoutSubviews
{
[super layoutSubviews];
self.holderViewLeadingConstraint.constant = 320;
}
#pragma mark - Setters
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
[super setBackgroundColor:backgroundColor];
self.leftBarButtonContainerView.backgroundColor = backgroundColor;
self.rightBarButtonContainerView.backgroundColor = backgroundColor;
}
- (void)setLeftBarButtonItem:(UIButton *)leftBarButtonItem
{
[super setLeftBarButtonItem:leftBarButtonItem];
}
- (void)setLeftBarButtonItemWidth:(CGFloat)leftBarButtonItemWidth
{
// self.leftBarButtonContainerViewWidthConstraint.constant = leftBarButtonItemWidth;
[self setNeedsUpdateConstraints];
}
- (void)setRightBarButtonItem:(UIButton *)rightBarButtonItem
{
[super setRightBarButtonItem:rightBarButtonItem];
}
- (void)setRightBarButtonItemWidth:(CGFloat)rightBarButtonItemWidth
{
// self.rightBarButtonContainerViewWidthConstraint.constant = rightBarButtonItemWidth;
[self setNeedsUpdateConstraints];
}
- (void)setRightContentPadding:(CGFloat)rightContentPadding
{
// self.rightHorizontalSpacingConstraint.constant = rightContentPadding;
[self setNeedsUpdateConstraints];
}
- (void)setLeftContentPadding:(CGFloat)leftContentPadding
{
// self.leftHorizontalSpacingConstraint.constant = leftContentPadding;
[self setNeedsUpdateConstraints];
}
#pragma mark - UIView overrides
- (void)setNeedsDisplay
{
[super setNeedsDisplay];
[self.textView setNeedsDisplay];
}
//return the custom view that we are going to create next
- (JSQMessagesToolbarContentView_custom *)loadToolbarContentView
{
NSArray *nibViews = [[NSBundle bundleForClass:[JSQMessagesToolbarContentView_custom class]] loadNibNamed:NSStringFromClass([JSQMessagesToolbarContentView_custom class]) owner:nil options:nil];
return nibViews.firstObject;
}
after this you need to create add new .xib file name it as JSQMessagesToolbarContentView_custom.xib this file contains our small content view for the inputToolbar and more importantly set the out let connections as doing the demo example and aslo set the view class name to JSQMessagesToolbarContentView_custom. hear i can only add image of the custom view.
now create a outlet for leading constraint to reduce the size of the content view as give below,
and add the constraints outlet's as given in the demo. so if u add some constrians without modifying the base class it will give error or runtime error so edit the base class also
now go to JSQMessagesToolbarContentView.h and add the blow properties form JSQMessagesToolbarContentView.m just cut and past and make it public.
#interface JSQMessagesToolbarContentView : UIView
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftBarButtonContainerViewWidthConstraint;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightBarButtonContainerViewWidthConstraint;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftHorizontalSpacingConstraint;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightHorizontalSpacingConstraint;
//...rest of the code
now in the JSQMessagesInputToolbar.m file, in order to make the tool bar transperent for half of the splitview,
- (void)awakeFromNib
{
[super awakeFromNib];
//...rest of the code
[self setBackgroundImage:[UIImage new]//imageNamed:#"topbar"]
forToolbarPosition:UIToolbarPositionAny
barMetrics:UIBarMetricsDefault];
[self setShadowImage:[UIImage new] forToolbarPosition:UIBarPositionAny];
[self setBackgroundColor:[UIColor clearColor]];
}
thats it now run the project, and change the leading constraints constant you see below output,

iOS Rotation Gesture after animation

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.

UITap gesture only works on one view

I have the following code in a view controller:
#interface QuizController ()
#property (weak, nonatomic) IBOutlet UITextView *questionText;
#property (weak, nonatomic) IBOutlet UITextView *one;
#property (weak, nonatomic) IBOutlet UITextView *two;
#property (weak, nonatomic) IBOutlet UITextView *three;
#property (weak, nonatomic) IBOutlet UITextView *four;
#property(strong, nonatomic) NSMutableArray* possAnswers;
#end
#implementation QuizController
- (void)viewDidLoad {
[super viewDidLoad];
[self.manager setupGame];
self.possAnswers = [[NSMutableArray alloc] initWithObjects:#"a", #"b", #"c", #"d", nil];
}
//This listens for a tap on any of the TextViews and sets the background to Blue
- (IBAction)fourTapped:(id)sender {
NSLog(#"There's been a tap!");
[self blankBoxes];
if( [sender isKindOfClass:[UITapGestureRecognizer class]] )
{
UITapGestureRecognizer* tgr = (UITapGestureRecognizer*)sender;
UIView* view = tgr.view;
if( [view isKindOfClass:[UITextView class]]){
UITextView* tv = (UITextView*)view;
NSLog([tv text]);
[tv setBackgroundColor:[UIColor blueColor]];
}
}
}
-(void)blankBoxes
{
[_one setBackgroundColor:[UIColor whiteColor]];
[_two setBackgroundColor:[UIColor whiteColor]];
[_three setBackgroundColor:[UIColor whiteColor]];
[_four setBackgroundColor:[UIColor whiteColor]];
}
However, the only UITextView that gets highlighted is _one, no matter which view is tapped. I don't understand why this is. I'd be really grateful if anyone could point this out to me.
Gesture recognizers attach to one and only one view. That's by design. If you want a single gesture recognizer that lets you recognize taps on multiple views, you have to attach it to a common, enclosing superview, then look at the tap coordinates and figure out which view was tapped.
Normally you just create a different gesture recognizer for each view that needs to responds to taps.
Add the UIGestureRecognizer to a UIView that contains your subviews. After that use CGRectContainsPoint() to check if the tap location is in one of the subviews, then continue from there.
CGPoint *touchLocation = [gesture locationInView:self];
for(UIView *view in self.subviews){
if(CGRectContainsPoint(view.frame, touchLocation))
//Your code here
}

UIView is retaining subviews after dealloc ARC

I have a problem with one of my views retaining its subviews. The main view displays 'tables' in a restaurant, and loads a subview to display this 'table'.
When the main view is deallocated, the tables seem to remain allocated to memory. I have searched everywhere to try and find a solution to this as I just can't seem to fix it myself.
Firstly the code for the 'table' view:
#protocol tableDelegate <NSObject>
#required
- (void)tablePress:(id)sender;
- (void)tableSelect:(id)sender;
#end
#interface tablevw : UIView
{
CGPoint currentPoint;
}
-(void)changeOrderImage;
-(id)initWithFrame:(CGRect)frame teststring:(NSString *)ts;
#property (nonatomic, weak) IBOutlet UIView *view;
#property (nonatomic, weak) IBOutlet UILabel *numberLabel;
#property (nonatomic, weak) IBOutlet UILabel *nameLabel;
#property (nonatomic) BOOL isEdit;
#property (nonatomic) BOOL hasOrder;
#property (nonatomic, strong) NSMutableDictionary * orderNumbers;
#property (nonatomic) int orderNumber;
#property (nonatomic, strong) NSString *teststring;
#property (nonatomic, weak) IBOutlet UIImageView *tableImage;
#property (nonatomic, weak) id<tableDelegate> delegate;
#end
And the init method for the view:
- (id)initWithFrame:(CGRect)frame teststring:(NSString *)ts{
self = [super initWithFrame:frame];
if (self) {
orderNumbers = [[NSMutableDictionary alloc]init];
isRemote = FALSE;
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"tabletopRound" ofType:#"png"];
UIImageView * iV =[[UIImageView alloc] initWithImage:[[UIImage alloc] initWithContentsOfFile:filePath]];
self.tableImage= iV;
tableImage.frame = CGRectMake(0, 0, 121, 121);
locked = FALSE;
[self addSubview:tableImage];
UITapGestureRecognizer *doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTap)];
doubleTapGestureRecognizer.numberOfTapsRequired = 2;
[self addGestureRecognizer:doubleTapGestureRecognizer];
UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(35, 50, 48, 20)];
numberLabel = label;
[numberLabel setTextColor:[UIColor blackColor]];
[numberLabel setBackgroundColor:[UIColor clearColor]];
[numberLabel setText:ts];
[self addSubview:numberLabel];
UILabel * labelTwo = [[UILabel alloc] initWithFrame:CGRectMake(24, 123, 70, 20)];
nameLabel = labelTwo;
[nameLabel setTextColor:[UIColor whiteColor]];
[nameLabel setBackgroundColor:[UIColor clearColor]];
[self addSubview:nameLabel];
self.userInteractionEnabled = YES;
self.tag = [ts intValue];
}
return self;
}
Finally, in the 'main' view the tables are added like so:
NSString *myString = [results stringForColumn:#"table_number"];
tablevw * tableView = [[tablevw alloc] initWithFrame:CGRectMake(x-60.5, y-60.5, 121, 121) teststring:myString];
tableView.delegate = self;
[self.view addSubview: tableView];
The delegate is set to Nil when the main view is dealloced. I have over ridden the dealloc method to log the dealloc calls to me - it is being called on the 'main' view but not on the 'table' view.
Thanks for your help
In the dealloc method try
for (UIView *view in [self.view subviews]) {
[view removeFromSuperview];
}
It would be even better if u can try
[tablevw removeFromSuperview], tablevw = nil;
I can see that your tablevw has a week reference to his delegate and that should be enough for to release the parent, but just for argue sake trie to nil tablevws delegate. Create an array of tablevw instances you created (or tag them as already suggested), then before you release parent remove them from superview, set their delegate to nil and kill the array. See what happenes, maybe that will help...

Resources