UI handling according to Multiple language With out Autolayouts - ios

I have two languages in my application which are English and Arabic.
How do I change the UI according to the selected Language With out Autolayouts?
For example:
If the user selects English we have to display text left to right.
If the user selects Arabic we have to display the text right to left.
The display rules includes Tableviews, Labels, Buttons, Images, Collection views, Views, Textfields, etc...

There are lot of scenarios you need to consider while working for
app which is changing the UI within app for Arabic. I used following
code. This will work perfectly. You need to handle it properly for
scrollView and UITableView. Need to be sure that UI is proper while pushing and
poping from view controllers.
- (void)reloadUI {
if (languageIsChanged == YES) {
[self reloadRTLView:self.view];
}
}
- (void)reloadRTLView:(UIView *)view {
[self changeViewRTL:view];
for (UIView *tempView in view.subviews) {
[self reloadRTLView:tempView];
}
}
- (void)changeViewRTL:(UIView*)tempView {
for (NSLayoutConstraint *constrain in tempView.constraints) {
NSLayoutAttribute firstAttribute = constrain.firstAttribute;
NSLayoutAttribute secondAttribute = constrain.secondAttribute;
if ((firstAttribute == NSLayoutAttributeLeading || firstAttribute == NSLayoutAttributeTrailing) && (secondAttribute == NSLayoutAttributeLeading || secondAttribute == NSLayoutAttributeTrailing)) {
if (firstAttribute == NSLayoutAttributeLeading) {
firstAttribute = NSLayoutAttributeTrailing;
} else if (firstAttribute == NSLayoutAttributeTrailing) {
firstAttribute = NSLayoutAttributeLeading;
}
if (secondAttribute == NSLayoutAttributeLeading) {
secondAttribute = NSLayoutAttributeTrailing;
} else if (secondAttribute == NSLayoutAttributeTrailing) {
secondAttribute = NSLayoutAttributeLeading;
}
constrain.constant *= -1;
NSLayoutConstraint *constrainNew = [NSLayoutConstraint constraintWithItem:constrain.firstItem attribute:firstAttribute relatedBy:constrain.relation toItem:constrain.secondItem attribute:secondAttribute multiplier:constrain.multiplier constant:constrain.constant];
[tempView removeConstraint:constrain];
[tempView addConstraint:constrainNew];
}
}
}
Up vote if this help you.

In swift we can use UIView.appearance().semanticContentAttribute = .forceRightToLeft for Right to left(Arabic).

Related

UIView subview autolayout issue

I have UITableView Cell in which 4 UIViews are arranged in a vertical order like this,
In each UIView, I have two Labels like this,
I have taken a IBOutlet for Height Constraint of all of the Four UIViews and I want to make the height 0 of the view when data is not available in that view. The problem is View is not getting 0 height due to the 10 px bottom constraint of UILabel inside that View.
What i am getting is like this,
Code where constraints are handled
NSString *toName = [self jointNameFromArray:evidence.arrTo];
if ([Utility isEmptyString:toName]) {
cell.toViewHeight.constant = 0;
}
else {
cell.lblToName.text = toName;
}
NSString *fromName = [self jointNameFromArray:evidence.arrFrom];
if ([Utility isEmptyString:fromName]) {
cell.fromViewHeight.constant = 0;
}
else {
cell.lblFromName.text = fromName;
}
NSString *ccName = [self jointNameFromArray:evidence.arrCc];
if ([Utility isEmptyString:ccName]) {
cell.ccViewHeight.constant = 0;
}
else {
cell.lblCCName.text = ccName;
}
NSString *custName = [self jointNameFromArray:evidence.arrCustodian];
if ([Utility isEmptyString:custName]) {
cell.custViewHeight.constant = 0;
}
else {
cell.lblCustName.text = custName;
}
[cell layoutIfNeeded];
If you're targeting iOS 9+, I'd suggesting using UIStackView, as it does exactly what you want just by setting the .hidden property of your view to true.
https://www.raizlabs.com/dev/2016/04/uistackview/
If you are unable to use UIStackView, you will need to set one of the vertical padding constraints to a lower priority than the height constraint. You will also need to set .clipsToBounds to true.
Set constraint programatically and write logic as u want it is good idea and dont forgot to set layoutifnedded.

How can I apply multiplier to all my constraints created on nib files?

I've pretty much finished my project, only to realize fonts and spaces in between look weird in iPhone 6Plus.
I've created all my constraints to drag and drop on XIB files. My colleague added these codes on a global function so I can apply the multipliers:
- (float)constraintScale {
if (IS_STANDARD_IPHONE_6_PLUS) {
return 1.29;
}
return 1.0;}
- (float)textScale {
if (IS_STANDARD_IPHONE_6_PLUS)
{
return 1.16;
}
return 1.0; }
My problem is now having to drag each (more than 100) constraints into my code and applying each with these multiplier and scale. Is there a more convenient way to add multipliers to my XIB files? I've thought about subclassing but that will probably take the same amount of time?
self.view.constraints
will give you all the constraints on the view.
for(NSLayoutConstraint *c in self.view.constraints)
{
c.multiplier = [self constraintScale];
}
probably work. Hope it helps.
edit:
sorry for readonly issue. another way to get rid of this problem may be;
NSMutableArray *constraintsNew = [NSMutableArray new];
for (NSLayoutConstraint *c in self.view.constraints)
{
NSLayoutConstraint *newC = [NSLayoutConstraint constraintWithItem:c.firstItem
attribute:c.firstAttribute
relatedBy:c.relation
toItem:c.secondItem
attribute:c.secondAttribute
multiplier:[self constraintScale]
constant:c.constant];
[constraintsNew addObject:newC];
}
[self.view removeConstraints:self.view.constraints];
[self.view addConstraints:constraintsNew];

UITextView Inside UITableViewCell Auto-Layout iOS 6 vs. iOS 7

I'm building a messaging screen as part of the app I'm writing. Currently, it is a UITableView containing cells that are my own custom subclass of UITableViewCell. I am using auto layout with constraints on the cell defined in Interface Builder. My messaging mimics, or attempts to mimic, the default messaging app. There are three main components of each table view cell: a UITextView containing the message body and two additional UILabels, one for the sender's name and/or time stamp, and the other for delivered/read receipts.
Now, using auto layout combined with tableView:heightForRowAtIndexPath: on my view controller, the message's text view in each table view cell is supposed to grow according to how large the message is (I use sizeWithFont:constainedToSize:lineBreakMode at present - I know it's deprecated but the replacements don't work on iOS 6 and are also flaky as of yet). This works fine when both labels and the text view are all present on the UI. However, in an individual message thread, I remove the delivered/read label using removeFromSuperview for all message cells but the final message (if said final message is sent by you). This does not cause adverse affects on iOS 7, but on iOS 6, any cell that has a label removed causes the text view to have a height of 0.0 (confirmed by debug outputs). Programmatically re-adding the label and appropriate auto layout constraints seems to fix it, but in any cell where that label is removed, even if I calculate a positive height for the text view in tableView:heightForRowAtIndexPath:, the text view height is zero, and the remaining label ends up shifted upwards to 'appear' to overwrite the text view.
I guess removing a view from its superview is the main culprit here, but I don't understand why this would occur only on iOS 6 instead of both 6 and 7.
Now, the code. Here is my cellForRowAtIndexPath: and heightForRowAtIndexPath: methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * sentMessageCellIdentifier = #"sentMessageCell";
static NSString * receivedMessageCellIdentifier = #"receivedMessageCell";
MessageCell * cell;
Message * messageObject = [associatedThread.messages objectAtIndex:indexPath.row];
GroupMember * selfMm = [associatedThread.parentGroup groupMemberForUser:[ApplicationInstance getInstance].currentUser];
if ([messageObject.sender isEqualToGroupMember:selfMm]) {
// Sent
cell = (MessageCell *) [tableView dequeueReusableCellWithIdentifier:sentMessageCellIdentifier];
cell.sentTimeLabel.text = [UtilityFunctions messageFriendlyFormattedDateTimeForDate:messageObject.messageTime];
if ([messageObject isEqualToMessage:[associatedThread.messages lastObject]]) {
cell.deliveredReadByLabel.text = #"Sent";
} else {
cell.deliveredReadByLabel.text = nil;
}
} else {
// Received
cell = (MessageCell *) [tableView dequeueReusableCellWithIdentifier:receivedMessageCellIdentifier];
[cell setSenderAndDateTimeForSender:messageObject.sender date:messageObject.messageTime];
}
// Read by label
NSString * readByText = nil;
if (associatedThread.parentGroupMember == nil) {
// Group thread
if (messageObject.readBy.count == 0) {
if (![messageObject.sender isEqualToGroupMember:selfMm]) {
readByText = #"Read by: only you";
}
} else {
NSInteger readByCount = messageObject.readBy.count;
NSInteger toSubtract = [messageObject.sender isEqualToGroupMember:selfMm] ? 1 : 2;
if (readByCount == associatedThread.members.count - toSubtract) { // If everyone read it (minus you and the sender)
readByText = #"Read by everyone";
} else {
GroupMember * randRbm = [messageObject.readBy firstObject];
if (messageObject.readBy.count == 1) {
cell.deliveredReadByLabel.text = [NSString stringWithFormat:#"Read by: %#", randRbm.user.displayName];
} else if (messageObject.readBy.count > 1) {
cell.deliveredReadByLabel.text = [NSString stringWithFormat:#"Read by: %# + %d", randRbm.user.displayName, messageObject.readBy.count - 1];
}
cell.deliveredReadByLabel.userInteractionEnabled = YES;
[cell.deliveredReadByLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapReadByLabel:)]];
}
}
} else {
// One-on-one individual thread
if ([messageObject isEqualToMessage:[associatedThread.messages lastObject]] &&
[messageObject.sender isEqualToGroupMember:selfMm]) {
if (cell.deliveredReadByLabel.superview == nil) {
[cell.contentView addSubview:cell.deliveredReadByLabel];
// Auto-layout bindings
NSArray * constaints = #[[NSLayoutConstraint constraintWithItem:cell.deliveredReadByLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:cell.sentTimeLabel
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:1.0],
[NSLayoutConstraint constraintWithItem:cell.deliveredReadByLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:20.0],
[NSLayoutConstraint constraintWithItem:cell.deliveredReadByLabel
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:-20.0],
[NSLayoutConstraint constraintWithItem:cell.deliveredReadByLabel
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-5.0]
];
[cell addConstraints:constaints];
}
if (messageObject.readBy.count == 1) {
readByText = #"Read";
}
} else {
[cell.deliveredReadByLabel removeFromSuperview];
}
}
if (readByText != nil) {
cell.deliveredReadByLabel.text = readByText;
}
debugLog(#"%#", [messageObject isEqualToMessage:[associatedThread.messages lastObject]] ? #"YES" : #"NO");
debugLog(#"x,y [%f, %f] | w,h [%f, %f] - message view", cell.messageView.frame.origin.x, cell.messageView.frame.origin.y, cell.messageView.frame.size.width, cell.messageView.frame.size.height);
debugLog(#"x,y [%f, %f] | w,h [%f, %f] - sent time label", cell.sentTimeLabel.frame.origin.x, cell.sentTimeLabel.frame.origin.y, cell.sentTimeLabel.frame.size.width, cell.sentTimeLabel.frame.size.height);
debugLog(#"x,y [%f, %f] | w,h [%f, %f] - sender time label", cell.senderAndDateTimeLabel.frame.origin.x, cell.senderAndDateTimeLabel.frame.origin.y, cell.senderAndDateTimeLabel.frame.size.width, cell.senderAndDateTimeLabel.frame.size.height);
debugLog(#"x,y [%f, %f] | w,h [%f, %f] - delivered label", cell.deliveredReadByLabel.frame.origin.x, cell.deliveredReadByLabel.frame.origin.y, cell.deliveredReadByLabel.frame.size.width, cell.deliveredReadByLabel.frame.size.height);
// Message body
[UtilityFunctions setZeroInsetsForTextView:cell.messageView];
cell.messageView.text = messageObject.messageBody;
cell.messageView.scrollEnabled = YES;
cell.messageView.scrollEnabled = NO;
return cell;
}
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *) indexPath {
CGFloat totalHeight = 0.0;
Message * m = [associatedThread.messages objectAtIndex:indexPath.row];
// Top and bottom padding
totalHeight += 5.0 + 5.0;
// Height + padding between labels (and text view)
totalHeight += 14.0 + 14.0 + 1.0 + 1.0; // height + height + padding + padding
// Modify UI slightly if incoming message and one-on-one thread:
if (associatedThread.parentGroupMember != nil) {
totalHeight -= (14.0 + 1.0);
if ([m isEqualToMessage:[associatedThread.messages lastObject]]) {
if ([m.sender isEqualToGroupMember:[associatedThread.parentGroup groupMemberForUser:[ApplicationInstance getInstance].currentUser]]) {
totalHeight += (14.0 + 1.0);
}
}
}
NSString * bodyText = m.messageBody;
CGSize constraint = CGSizeMake(MESSAGE_TEXT_WIDTH_MAX, CGFLOAT_MAX);
CGSize sizeWithFont = [bodyText sizeWithFont:[UIFont systemFontOfSize:16.0] constrainedToSize:constraint lineBreakMode:NSLineBreakByWordWrapping];
totalHeight += sizeWithFont.height + 1.0; // 1.0 because iOS hates me
if ([m isEqualToMessage:[associatedThread.messages lastObject]]) {
debugLog(#"YES");
} else {
debugLog(#"NO");
}
debugLog(#"height: %f", totalHeight);
return totalHeight;
}
Here are the constraints I set in Interface Builder. Note the static width of the message text view:
And here is how it looks in iOS 6 (notes: the colors are for my own visual aid, it obviously will not stay that way, and simulator/device produces same results):
Here is the expected behavior in iOS 7, as I desire it to behave:
It's important to note the the height of the actual table view cell itself appears to be correct, but the text view is not adjusting accordingly, despite being able to I've tried adjusting my code in both methods above and attempted different techniques to no avail. I'm fairly certain that I need to use removeFromSuperview as that is the only way to both use auto layout and accommodate what I am trying to do. The delivered/read label gets removed under the following conditions:
There are two people in the message thread
The message is the latest message in the thread
That last message was sent by you
I know this is a terribly specific question, but if anyone has ideas as to why this occurs I would be grateful. Note that the text view is not editable, though it is selectable.
As always, thanks.
Edit: I also occasionally will get iOS 6 to throw Assertion failure in -[UITableViewCell layoutSublayersOfLayer:] even though my custom subclass doesn't implement that function. It has whined about constraints before, but that is a crap shoot to reproduce that error.
I solved this by giving in and just keeping both UILabels on the cell at all times. I'll just be clever about how I arrange the date and read bys. I'll leave this in case someone has a similar issue in the future.

Disable Autolayout Localization Behavior (RTL - Right To Left Behavior )

My application is localized in both English and Arabic.
Unfortunately, sometimes the autolayout behavior for localization is not required. By that, I mean reversing the leading and trailing spaces. I want to override this behavior. Is there any way to do that?
To make leading act always as left (and trailing always like right), i.e. make it language independent, you can remove checkmark on "Respect language direction" on all constrains.
You can find this checkmark in constrain settings in the Attributes inspector under "First Item" button.
The attributes leading and trailing are the same as left and right for left-to-right languages such as English, but in a right-to-left environment such as Hebrew or Arabic, leading and trailing are the same as right and left. When you create constraints, leading and trailing are the default values. You should usually use leading and trailing to make sure your interface is laid out appropriately in all languages, unless you’re making constraints that should remain the same regardless of language.
So, for your special cases, don't use leading and trailing, instead, explicitly use left and right when you create your constraints.
as in #Pavel answer, you should turn off 'Respect language direction' property. if you have lots of constraints, you can open xib or storyboard file in XML view and replace all 'leading' values with 'left' and all 'trailing' values with 'right' and you're done.
Try this
Create class for managing constraint in all views
#implementation RTLController
#pragma mark - Public
- (void)disableRTLForView:(UIView *)view
{
[self updateSubviewForParentViewIfPossible:view];
}
#pragma mark - Private
- (void)updateConstraintForView:(UIView *)view
{
NSMutableArray *constraintsToRemove = [[NSMutableArray alloc] init];
NSMutableArray *constraintsToAdd = [[NSMutableArray alloc] init];
for (NSLayoutConstraint *constraint in view.constraints) {
NSLayoutAttribute firstAttribute = constraint.firstAttribute;
NSLayoutAttribute secondAttribute = constraint.secondAttribute;
if (constraint.firstAttribute == NSLayoutAttributeLeading) {
firstAttribute = NSLayoutAttributeLeft;
} else if (constraint.firstAttribute == NSLayoutAttributeTrailing) {
firstAttribute = NSLayoutAttributeRight;
}
if (constraint.secondAttribute == NSLayoutAttributeLeading) {
secondAttribute = NSLayoutAttributeLeft;
} else if (constraint.secondAttribute == NSLayoutAttributeTrailing) {
secondAttribute = NSLayoutAttributeRight;
}
NSLayoutConstraint *updatedConstraint = [self constraintWithFirstAttribute:firstAttribute secondAtribute:secondAttribute fromConstraint:constraint];
[constraintsToRemove addObject:constraint];
[constraintsToAdd addObject:updatedConstraint];
}
for (NSLayoutConstraint *constraint in constraintsToRemove) {
[view removeConstraint:constraint];
}
for (NSLayoutConstraint *constraint in constraintsToAdd) {
[view addConstraint:constraint];
}
}
- (void)updateSubviewForParentViewIfPossible:(UIView *)mainView
{
NSArray *subViews = mainView.subviews;
[self updateConstraintForView:mainView];
if (subViews.count) {
for (UIView * subView in subViews) {
[self updateConstraintForView:subView];
[self updateSubviewForParentViewIfPossible:subView];
}
}
}
- (NSLayoutConstraint *)constraintWithFirstAttribute:(NSLayoutAttribute)firstAttribute secondAtribute:(NSLayoutAttribute)secondAttribute fromConstraint:(NSLayoutConstraint *)originalConstraint
{
NSLayoutConstraint *updatedConstraint =
[NSLayoutConstraint constraintWithItem:originalConstraint.firstItem
attribute:firstAttribute
relatedBy:originalConstraint.relation
toItem:originalConstraint.secondItem
attribute:secondAttribute
multiplier:originalConstraint.multiplier
constant:originalConstraint.constant];
return updatedConstraint;
}
#end
Add this code in to controller that should disable RTL behavior
RTLController *rtl = [[RTLController alloc] init];
[rtl disableRTLForView:self.view];
another easy way
Select any view from the storyboard
from the attributes inspector on the right select set "Semantic" to "Force Left-to-Right"

UITableViewCell becomes unresponsive

I have a popover screen, with inside it :
a label, that may or may not appear (title)
a search bar, that may or may not appear
a label, that may or may not appear, and has a variable height (help label)
a scrollview, that may or may not appear, and has a variable height (some infos about the following table)
a table view
In order to present something nice, in viewDidLoad, I move the various frames to place the objects correctly and not have unused spaces cluttering my popover. Besides, I then resize the table (to take the most place needed), and the popover via contentSizeInPopover (to avoid having a near-empty huge popover). All that resizing seems to work nicely, but I have one big problem : with all that resizing done, some cells of my UITableView become unresponsive. One or two cells, usually the second one, only respond if i tap in their outer corners, but the rest of the cell completely ignore any touches.
I've tried everything : moving all to viewWillAppear, letting the autoresize do its job (doesn't seem to work either), but I still have this problem every time. I've found that if I comment the lines involved with changing the frame of the table, or the ones in contentSizeInPopover, the problem stops, but then my view is messed up, so this ins't a fix.
If anyone could give me something to get out of this mess, that would be awesome.
- (CGFloat)getHeightWithoutTable {
return LIST_TITLE_HEIGHT + (self.searchBar.hidden ? 0 : LIST_SEARCH_BAR_HEIGHT) + (self.helpLabel.hidden ? 0 : self.helpLabel.frame.size.height + LIST_STD_SPACE) + (self.errorScrollView.hidden ? 0 : self.errorScrollView.frame.size.height + LIST_STD_SPACE);
}
-(void)viewDidLoad {
[super viewDidLoad];
self.tableViewOutlet.backgroundView = nil;
self.originData = [NSMutableArray array];
self.searchedData = [NSMutableArray array];
if (self.helper != nil) {
CGFloat heightOffset = 0;
// Content
self.originData = [self.helper getData];
self.tableData = [NSMutableArray arrayWithArray:self.originData];
// Title
NSString *title = [self.helper getPopoverTitle];
if (title == nil) {
self.popoverTitle.hidden = YES;
heightOffset -= LIST_TITLE_HEIGHT;
} else {
self.popoverTitle.text = [self.helper getPopoverTitle];
}
// Search
if ([self.originData count] [self getStdHeight] / 3){
self.helpLabel.lineBreakMode = UILineBreakModeTailTruncation;
[self.helpLabel sizeThatFits:CGSizeMake(self.helpLabel.frame.size.width, [self getStdHeight] / 3)];
}
heightOffset += (self.helpLabel.frame.size.height - LIST_HELP_STD_HEIGHT);
}
// Errors
if ([self.helper respondsToSelector:#selector(getErrors)]) {
self.errors = [self.helper getErrors];
}
if (self.errors == nil || [self.errors count] == 0) {
self.errorScrollView.hidden = YES;
self.errorBg.hidden = YES;
heightOffset -= LIST_ERROR_STD_HEIGHT + LIST_STD_SPACE;
} else {
[self createErrorView];
heightOffset += (self.errorScrollView.frame.size.height - LIST_ERROR_STD_HEIGHT);
}
// Table
CGFloat previewHeight = LIST_CELL_HEIGHT * [self.tableData count] + LIST_STD_SPACE;
CGFloat remainingHeight = LIST_MAX_HEIGHT - [self getHeightWithoutTable] - LIST_STD_SPACE;
CGFloat tableHeight = MIN(previewHeight, remainingHeight);
CGRect tableFrame = self.tableViewOutlet.frame;
self.tableViewOutlet.frame = CGRectMake(tableFrame.origin.x, tableFrame.origin.y + heightOffset, LIST_WIDTH, tableHeight);
// Selected items
if ([helper getSelectedObject] != nil){
int index = [self.tableData indexOfObject:[helper getSelectedObject]];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableViewOutlet scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
}
- (CGSize)contentSizeForViewInPopover {
if (self.navigationController) {
return CGSizeMake(LIST_WIDTH, LIST_MAX_HEIGHT);
} else {
CGFloat totalHeight = [self getHeightWithoutTable] + self.tableViewOutlet.frame.size.height + LIST_STD_SPACE;
return CGSizeMake(LIST_WIDTH, totalHeight);
}
}
(gist if you need some coloring to help you)
An image of the nib :
Just a shot in the dark, since you have not provided any code. If you are adding things to the UITableCellView, just remember that a lot of components have their UserInteractionEnabled set to NO, which will disable the ability to interact with it. Make sure that any items you add to the cell that potentially take up the space where you are tapping (presumably the center of the cell?) have their UserInteractionEnabled set to YES.
The reason why the edges might still work is that the UITableCellView consists of 3 main parts, so you are probably only changing the center part.
Post some code then we can have a better look.
Found the answer myself : the fact I was using a self-filled UIScrollView next to my UITableView seemed to be the problem. As soon as I replaced the UIScrollView by a proper UITableView, the problem disappeared.

Resources