Swift - Remove width with condition? - ios

I'm coding a social network, which can permit to like and comment pictures.
However, I have a problem with that part ...
If I have one (or more) like and one (or more) comment, it's ok:
If I have one (or more) comment but no like, the user interface is not very good ...
I would like to move the button "2 comments" on the left when there is no like. I thought to do that by giving 0 width to the "1 like" button. However, the width it's not all the time the same. If I have 1 like or 12543 likes, the width will be different.
I have no idea about how I could correct this problem.
Do you have any idea please ?

you can create a custom component that will have a view, and two uilabels as a subviews if the view. the first uilabel will contain the number and the second the title ("comments").
the constraints of the uilables should be as follow:
first table leading == view.leading, first label trailing == second label leading, second label.trailing == view.trailing. view.height == "some constant". In this case the width of the view will be according to the labels width.
Now you should add those custom views to your view. In your case it will be two or one custom components, one for the likes and one for the comments.
add those custom view to tmprorary array and do the following code:
var prevLayoutedView : UIView = self
for (index, customView) in custumViewsArr.enumerated()
{
customView.leadingAnchor.constraint(equalTo: prevLayoutedView.leadingAnchor)
if index == custumViewsArr.enumerated.count
{
customView.trailingAnchor.constraint(equalTo: prevLayoutedView.trailing)
}
}
you will need to adjust some constants and compression resistance and hugging priority but this should assist you to solve it
UPDATE:
you can use less generic solution and not using a custom component but just using uilable. and then use the same for loop

first easy solution is to keep single label for both if not clickable
otherwise with two labels,
my constraints for two label is,
1. Constraints for like label:
2. Constraints for comment label
#IBOutlet var cnLeadingToView: NSLayoutConstraint!
#IBOutlet var cnLeadingToLikelbl: NSLayoutConstraint!
assign this outlet in storyboard for leading constraints of comment label then do this
if cntLikes == 0 { // temp variable
//hide the like lable
self.lblLikes.isHidden = true
self.cnLeadingToLikelbl.isActive = false // deactivate the leading constraint with like label
self.cnLeadingToView.isActive = true // activate the leading constraint with view
self.cnLeadingToView.constant = 15 // give leading space constant
}else {
self.lblLikes.isHidden = false
self.cnLeadingToLikelbl.isActive = true
self.cnLeadingToView.isActive = false
}

As #dahiya_boy said, I had to change the like label relationship from equalTo to lessthanequalto.
I put 375 by default and 0 if the is no like. It was that simple.
Thank you very much everybody !

Related

How to set alignments right for dynamic UIStackView with inner XIB

My original ViewController consists of only one scrollView like this:
Now I also have my own xib file (CheckBoxView) which mainly consists of one button, see this screenshot:
I dynamically create some UIStackViews and add them to the ScrollView Inside these UIStackViews I add multiple instances of my xib file.
What I want to achieve is, that the StackViews are just vertically stacked. And inside the StackViews the UIViews from my xib-file should also be vertically stacked.
At the moment it looks like this:
So the xib-Views are not in the whole view. Since I am using multi-os-engine I can't provide swift/obj-c code. But here is my Java-Code:
for (ItemConfiguration config : itemInstance.getConfigurations()) {
List<DLRadioButton> radioButtons = new ArrayList<DLRadioButton>();
UIStackView configView = UIStackView.alloc().initWithFrame(new CGRect(new CGPoint(0, barHeight), new CGSize(displayWidth, displayHeight - barHeight)));
configView.setAxis(UILayoutConstraintAxis.Vertical);
configView.setDistribution(UIStackViewDistribution.EqualSpacing);
configView.setAlignment(UIStackViewAlignment.Center);
configView.setSpacing(30);
for (ConfigurationOption option : config.getOptions()) {
UIView checkBox = instantiateFromNib("CheckBoxView");
for (UIView v : checkBox.subviews()) {
if (v instanceof DLRadioButton) {
((DLRadioButton) v).setTitleForState(option.getName(), UIControlState.Normal);
//((DLRadioButton) v).setIconSquare(true);
radioButtons.add((DLRadioButton) v);
}
}
configView.addArrangedSubview(checkBox);
}
// group radiobuttons
//groupRadioButtons(radioButtons);
configView.setTranslatesAutoresizingMaskIntoConstraints(false);
scrollView().addSubview(configView);
configView.centerXAnchor().constraintEqualToAnchor(scrollView().centerXAnchor()).setActive(true);
configView.centerYAnchor().constraintEqualToAnchor(scrollView().centerYAnchor()).setActive(true);
}
private UIView instantiateFromNib(String name) {
return (UIView) UINib.nibWithNibNameBundle(name, null).instantiateWithOwnerOptions(null, null).firstObject();
}
How do I need to set the Alignments etc. to Achieve what I want. It should look like this:
I don't know if there is a reason to not use UITableView, that i highly recommend for your case. In case it's not possible, below you can find some pieces of advice that should help.
If you use Auto Layout, you should set constraints for all views instantiated in your code. The constraints must be comprehensive for iOS to know each view's position and size.
Remove redundant constraints
configView.centerXAnchor().constraintEqualToAnchor(scrollView().centerXAnchor()).setActive(true);
configView.centerYAnchor().constraintEqualToAnchor(scrollView().centerYAnchor()).setActive(true);
These two constraint just doesn't make sense to me. You need the stackviews be stacked within you ScrollView, but not centered. If i understand you goal correctly, this should be removed
Set width/x-position constraints for UIStackViews
configView.setTranslatesAutoresizingMaskIntoConstraints(false);
scrollView().addSubview(configView);
Right after a stack view is added to the ScrollView, you need to set up constraints for it. I'll provide my code in swift, but it looks quite similar to what your Java code is doing, so hopefully you'll be able to transpile it without difficulties:
NSLayoutConstraint.activate([
configView.leadingAnchor.constraintEqualToAnchor(scrollView.leadingAnchor),
configView.trailingAnchor.constraintEqualToAnchor(scrollView.trailingAnchor)
]);
Set height constraints for UIStackViews
StackViews doesn't change their size whenever you add arranged view in it. So you need to calculate a desired stackview size yourself and specify it explicitly via constraints. It should be enough to accommodate items and spaces between them. I suppose that all items should be of the same size, let it be 32 points, then height should be:
let stackViewHeight = items.count * 32 + stackView.space * (items.count + 1)
And make new height constraint for the stack view:
configView.heightAnchor.constraint(equalToConstant: stackViewHeight).isActive = true
Set y-position for UIStackView
This is a little bit more challenging part, but the most important for the views to work properly in a scroll view.
1) Change loop to know the index of a UIStackView
A scroll view should always be aware of height of its content, so you need to understand which stack view is the top one, and which is the bottom. In order to do that, you need to change for each loop to be written as for(;;) loop:
for (int i = 0; i < itemInstance.getConfigurations().length; i++) {
ItemConfiguration config = itemInstance.getConfigurations()[i]
...
}
I'm not aware of which type your array is, so if it doesn't have subscript functionality, just replace it with corresponding method.
2) Set top anchor for stack views
For the first stack view in the array, top anchor should be equal to the scroll view top anchor, for others it should be bottom anchor of the previous stack view + spacing between them (say, 8 points in this example):
if i == 0 {
configView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor, constant: 8).isActive = true
} else {
let previousConfigView = itemInstance.getConfigurations()[i - 1]
configView.topAnchor.constraintEqualToAnchor(previousConfigView.bottomAnchor, constant: 8).isActive = true
}
3) Set bottom anchor for the last stack view
As was said - for the Scroll View to be aware of content size, we need to specify corresponding constraints:
if i == itemInstance.getConfigurations() - 1 {
configView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor, constant: 8).isActive = true
}
Note: please be advised, that all constraints should be set on views that are already added to the scroll view.

Constraint to view after nearest neighbour removed

Sorry for the title if it's not the best but I really don't know how to explain this in a few words.
So, what I have is a view with a label and a image. There are two casses. One in which I don't need the image, just the label(the most ussual one). And one where I need both the image and the label and at some point I have to remove the image through an animation(this one is handled).
Now. for the one where I need just the label I was thinking to play with the constraints. I want to have the spacing for the left 15 and the constraint to go to the superview, but also when there is a image I want the constraint to go to the image. I'll add a image to make it more clear. How can I accomplish this?
Actually you can do the same thing from the XIB also.
Select the leadingConstraint outlet from the XIB and create the outlet for the constraint.
It will create a property of type NSLayoutConstraint, then set the value to 0 in case where you don't want to show ImageView
self.imageViewLeadingConstraint.constant = 0;
Repeat the same process for ImageView Width also.
For that you should bind (Take #IBOutlet) UIImageView's width constrains AND leading constrains and you need to manage it in cellForRowAtIndexPath like
if isOnlyLabel { // "isOnlyLabel" it's just for understanding
// Here you have only label not image
// Set image with constraint = 0 and leading = 0
}
else {
// Here you have label + image
// Set image with constraint = 60 and leading constraint = 15 Or as you want
}

Re-position uilabel

I wanted the "number" & "status" to display on the left if there is not QRCode display (constraints has been set for image & labels.)
Result without QRCode:
.
The result with QRCode will be:
IT IS very easy just make width Constraint of QRcode image, if QR code is not available than make widht constant = 0,
#IBOutlet weak var width: NSLayoutConstraint!
if (your condition)
{
width.constant = 0
}
else
{
width.constant = 30
}
You can do that with a second pair of constraints attached to left side of a view.
Add outlets to that constraints and set constant value to desired offset(from code), if there is no QRCode View.
Another way is to add MoreThanOrEquel constraint to left side (e.g >= 10)
Than another constraint with constant value 10, but with lower priority.
When you remove QRCode view, it will destroy it constraints and constraints to left side will affects and move yours labels to left.
You could make constraint for labels that way:
I.e. make constraint for image leading to left side + image width, both labels also have leading to left side. Then if there is no QR Code adjust labels leading constraints to value you need.
Or you could make both labels leading to image view and set the image view width constraint to 0.
This Problem is very simple , can achieve using UIStackView just making hide/Show.
For Ex: If QR-code is present show or else hide QR-Code.
Label (Number and status and image) will automatically get adjust. Following is the screen short for better understanding.
IBOutlet UIView *qr_view; // Image or View
Case 1 :QR Present
qr_view.hidden = NO;// Show
Case 2: QR Not Present
qr_view.hidden = YES;// hide

Xcode; Set a autolayout constraint to a defined variable value

Is it possible to define a variable, and set a constraints constant value to that variable?
Thereby making it possible to change many constraints by just changing the variable. I think I saw someone do this directly from interface builder ones?
EDIT:
There is a constraint between each label. I need a method to change all of these constraints, so they get a the same value. Is this possible?
If I use a outlet collection, I will have to iterate through all the constraints, and change the value for each. I'm looking for a method like this:
// SEUDO!!
lineSeperationWidth = 31 // changes all 4 constraints.
The NSLayoutContraints are all separate objects, and Xcode provides no way to set the constant value of multiple constraints with the same variable. The closest you can get is to use an #IBOutlet collection and then use the didSet property observer for your variable holding the common space value to update each of the constraints in the collection.
Here is an example I created which spaces out the labels based upon a random value that I set to the space property each time the Go button is pressed:
class ViewController: UIViewController {
#IBOutlet var spacers: [NSLayoutConstraint]!
var space: CGFloat = 20.0 {
didSet {
spacers.forEach { $0.constant = space }
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func spaceOut(sender: AnyObject) {
// set space to a random value
space = CGFloat(arc4random_uniform(30)) + 20.0
}
}
Each of the vertical space constraints between the labels is connected to the #IBOutlet collection spacers. Here is what the Connections Inspector shows:
Here it is in action:
Yes it is! You can use the document outline view to find the constraint you want to use as a variable. Once you have it, CTRL + Drag from the constraint in the document outline view to your code to make the outlet. Then you can change the constraints in code by using self.constraint.constant = 31.
To space all the views out equally, you can put UILayoutGuides in between each of the labels and constrain all their heights to be equal. Then you can change the height of one of the layout guides and they will all change to match it.

How to easily collapse vertical space around label when its text is nil?

Suppose I have three labels that are laid out below each other in a column. The uppermost label's top edge is pinned to the superview's top edge. All following labels' top edges are pinned to the preceding label's bottom edge. The leading and trailing edges of all labels are pinned to the leading and trailing edge of the superview. Here's what it looks like in Interface Builder (I added a blue background on every label to visualize its extent).
In the simulator the result looks like this.
All labels are connected to outlets in a view controller.
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
When I set the text of label2 to nil
label2.text = nil
the label itself collapses.
However, the top and bottom spaces around the label do not collapse. This is evident by the fact that there is no blue background on the middle label in the last screenshot. As a result, the space between label1 and label3 is double the space of the layout in the first screenshot.
My question is - on iOS8 - what is the easiest way to collapse either the middle label's top or bottom space so that the two remaining labels still use the vertical spacing defined in the original layout? To be clear, this is the result I want to achieve.
Options I've found so far:
Bottom/Top Spacing Constraint Outlet
Define an outlet for the middle label's top or bottom spacing constraint.
#IBOutlet weak var spacingConstraint: NSLayoutConstraint!
Store the constraint's initial constant into a variable (e.g. in awakeFromNib or viewDidLoad).
private var initialSpacing: CGFloat!
override func viewDidLoad() {
initialSpacing = spacingConstraint.constant
...
Set the constraint's constant to zero whenever the text is set to nil or back to its initial value when the text is not nil.
spacingConstraint.constant = label2.text == nil ? 0 : initialSpacing
This approach feels a bit clumsy since it requires two additional variables.
Height Constraint Outlet
Set the vertical spacing around the middle label to zero and increase its height by the same amount. Define an outlet for the height constraint and proceed as above, setting the height to zero when the text is nil and back to it's initial value when the height is not nil.
This is still as clumsy as the previous approach. In addition, you have to hardcode the spacing and cannot use the built-in default spacings (blank fields in Interface builder).
UIStackView
This is not an option since UIStackView is only available on iOS 9 and above.
I'm using this UIView category for this purpose.
It extends UIView by adding two more property named fd_collapsed and fd_collapsibleConstraints using objective-c runtime framework. You simply drag constraints that you want to be disabled when fd_collapsed property set to YES. Behind the scene, it captures the initial value of these constraints, then set to zero whenever fd_collapsed is YES. Reset to initial values when fd_collapsed is NO.
There is also another property called fd_autocollapsed
Not every view needs to add a width or height constraint, views like UILabel, UIImageView have their Intrinsic content size when they have content in it. For these views, we provide a Auto collapse property, when its content is gone, selected constraints will collapse automatically.
This property automatically sets fd_collapsed property to YES whenever specified view has content to display.
It's really simple to use. It's kinda shame that there is no builtin solution like that.
Your solutions are good enough for me and I'd do Bottom/Top Spacing Constraint Outlet solution but since you want something different. You can use this third party: https://github.com/orta/ORStackView It has iOS7+ support and do exactly what you need.
This is low-key a pain all perfectionist devs learn about when trying to stack a bunch of labels. Solutions can get too verbose, annoying to folow, and really annoying to implement (ie. keeping a reference to the top constraint... gets annoying once you do it multiple times, or just change the order of the labels)
Hopefully my code below puts an end to this:
class MyLabel: UILabel {
var topPadding: CGFloat = 0
override func drawText(in rect: CGRect) {
var newRect = rect
newRect.origin.y += topPadding/2
super.drawText(in: newRect)
}
override var intrinsicContentSize: CGSize {
var newIntrisicSize = super.intrinsicContentSize
guard newIntrisicSize != .zero else {
return .zero
}
newIntrisicSize.height += topPadding
return newIntrisicSize
}
}
Usage:
let label = MyLabel()
label.topPadding = 10
// then use autolayout to stack your labels with 0 offset
Granted, its only for top padding, but that should be the only thing you need to layout your labels properly. It works great with or without autolayout. Also its a big plus not needing to do any extra mental gymnastics just to do something so simple. Enjoy!

Resources