Setting space within certain elements within a stackview - ios

Essentially I am trying to create a UIStackView with some elements that has top margins and bottom margins. For example
| item one |
| item two |
| |
| itemfour |
I have tried to add a spacerView :
let textField1 = UITextField(text: "one")
let textField2 = UITextField(text: "two")
let textField3 = UITextField(text: "four")
let spacerView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
viewStack.addSubView(textField1)
viewStack.addSubView(textField2)
viewStack.addSubView(spacerView)
viewStack.addSubView(textField3)
However, the height that i am setting the spacerView does not seem to effect the space that is being created in the stack view.
PS: If there is a better way of adding top and bottom margins between two items, I am definitely open to it as adding a UIView feels really hacky

You could:
Make a spacer view that's a UILabel with text of a single space (" "). This will give you a blank row that's the same height as the rest of your text. This will work if the distribution is set to fillEqually (it works in other modes too, but I mention it because distribution matters in option #2 below).
or,
Create a subclass of UIView and override its intrinsicContentSize, returning a CGSize that specifies the height you want. Don't set this view's frame (i.e. let the UIStackView manage the view's frame). Note that this method only works if the distribution of the UIStackView is not set to fillEqually.

Related

Making the controller extend with the amount of text in UILabel in iOS

I'm quite new to Swift and iOS. I'm working on just getting things working so ignore the terrible UI. At the moment my text runs off the screen of the controller and I would like it make it so that depending on how long the text is the controller will allow for scrolling to accommodate it.
How can I do this with Swift and iOS?
First, you should place all your subviews (UILabel, UIImage etc.) in a UIScrollView.
Then, you have two options:
Springs & Struts: Once the frames of all subviews are set you can update the scroll view's contentSize property so it allows you to scroll. You can do this preferably in viewDidAppear method as all the frames have their exact value in there:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
scrollView.contentSize = CGSize(width: view.frame.width, height: bottomLabel.frame.maxY)
}
AutoLayout: With Autolayout, you no longer need to set the contentSize property explicitly. Just make sure you set vertical and width constraints correctly so AutoLayout will infer the contentSize automatically. Your subviews inside scrollView should be laid out as follows:
Scroll View
----------------
| Top Label |
| ------------ | v:10pt
| |
| Image | image.width = view.width
| |
| ------------ | v:10pt
| Bottom Label |
----------------
You can download the test project and see the constraints I set in the Interface Builder.
Here is the result I got:
Put everything you want to scroll inside of a UIScrollView

How to center the subviews of UIScrollView

I'm a beginner in creating a custom view. I'm trying to create a custom UIView with a scrollview and buttons that will look like this:
I'm adding a view(view with label of page number) inside of scrollView depending on the the number of pages. Is that how it should be?
Currently it looks like this:
My question is how can I center the subviews of scrollview? and next is what's wrong with this code? Why is that I can only see 1 label inside the view? and the other doesn't show up. How can I scroll to the selected page if the page number is not visible already in the scrollview?
Here's my code:
func addPageNumberViewWithCount(count: Int) {
var pageNumberViewX: CGFloat! = 0
let pageNumberViewDistance: CGFloat! = 10
for i in 1...count {
let pageNumberView = UIView(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
pageNumberView.backgroundColor = UIColor.redColor()
pageNumberView.layer.cornerRadius = pageNumberView.frame.height / 2
pageNumberView.layer.masksToBounds = true
pageNumberView.clipsToBounds = true
// add number label
let label = UILabel(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
label.center = pageNumberView.center
label.text = "\(i)"
label.textAlignment = .Center
pageNumberView.addSubview(label)
// update x for next view
pageNumberViewX = pageNumberView.frame.origin.x + pageNumberView.frame.width + pageNumberViewDistance
// add view inside scrollview
scrollView.addSubview(pageNumberView)
if i == count {
scrollView.contentSize = CGSizeMake(pageNumberViewX + pageNumberView.frame.width, 30)
}
}
}
Part of my answer will go to providing a solution to your question,and another part of my answer will go toward strongly suggesting that this not be the method you use to complete your desired tasks.
At this point, AutoLayout and Interface Builder have come a long way. Where they used to be difficult to use because of their inconsistency and unpredictability, they are now highly predictable and consistent as long as you understand the tools and how to use them.
Apple's suggested method for completing this task (which I mostly stand behind) is creating a .xib file (nib) to lay out the base components of the design, and to load the nib into the view or view controller whenever that design should be used. My question for you: have you tried this, or have you determined for some reason that this would be an unsatisfactory solution to your problem? AutoLayout exists to solve these problems not just in allowing you to achieve your desired solution in this one situation but to achieve it in other situations as well, with varying screen sizes and device types.
Now, if you were to simply ignore all of that and continue on your path, there would be a few good ways to handle your problem. One suggested solution I have:
1) Wrap your pageNumberView in another view. Constrain that view to the size of the scrollView. Doing this gives the scrollView content with which to base its scrollable content size, and gives the inner pageNumberView something to compare itself to.
2) Center the pageNumberView horizontally in its container (the new view that we just created).
Doing this, the page numbers should now center themselves in the container until they reach a size where they exceed the width of the scrollView. At that point, they will then continue to expand, making the area horizontally scrollable.
I can provide code examples of how you would do this, but frankly I would much prefer if you scrapped the idea of doing things this way and instead opted for the AutoLayout method at least, and perhaps even the Interface Builder method. I started out with iOS the same way you did, trying to do everything in code. It really isn't the best way to do things, at least with regard to iOS.
Edit: I've provided an example of how this would look in Interface Builder using UINib. I've populated the view with an example of 5 pages to show what it is like. I will see if I can make a GIF or something similar to show what each of the subviews look like.
For the OP, my suggestion would be this: Use this for reference, and go learn the constraints system. It is extremely unlikely that you will find success with iOS if you do not learn and utilize the constraints system. Coding in X values to a UIView's frame is only going to create a product with poor, inconsistent performance across devices, and will take much, much longer than it would to take the time to learn constraints.
Perhaps you should have a UICollectionView with a cell for each of these buttons. That's a better way of doing this, and you can lay it out again when the screen rotates and it changes width.
Those cells will layout offset to the left. You can solve that this way:
let pageNumberViewTotalWidth = 30 * count + (pageNumberViewDistance * count - 1)
self.collectionView.contentInset.left = (self.collectionView.frame.size.width - pageNumberViewTotalWidth) / 2
The labels aren't showing up because you're setting their frame's x to be the same as the page number view's x. It's frame should be relative to it's superview, in this case pageNumberView.
First Question of yours "how can I center the subviews of scrollview?"
Solution: lets suppose you have in total 50 pages and you want to show 5 pages at a time in the scrollview.
Then make 10 subviews of equal widths where each subview width will be equal to visible portion of the collection view that is
self.view.size.width - 2*(width of toggle button)
Then in each container view add 5 of your pageNumberView placed at equal distance
lets pageNumberViewWidth = container.width/5 - 2*margin
now pageNumberView frame will be (margin,0,pageNumberViewWidth,height)
In this way in each container view your pageNumberViews will be placed equally and it will look as if you have centred them.
Second Question "Why is that I can only see 1 label inside the view?"
Answer : Its because you are setting label frame incorrectly
let label = UILabel(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
Here label is the subview of pageNumberView So you have to set its frame according to its parent's view which is pageNumberView, so change it to
let label = UILabel(frame: CGRectMake(0, 0, 30, 30))
First time it was right because pageNumberViewX is 0 for first iteration after that it become some positive value which makes its frame shifted to right but its parent's width is small so its not visible to you.
Third Question : "How can I scroll to the selected page if the page number is not visible already in the scrollview?"
For this you need to find the frame of your selected page:
you can do that by using the offset that you used to create pageNumberView.
(width of each pageNumberView)*pageNumber = starting point of the required pageNumberView.
let frame : CGRect = CGRectMake(calculated offset above, 0,30, 30)
//where you want to scroll
self.scrollView.scrollRectToVisible(frame, animated:true)
I hope this will help you in solving your problem
Edit for first problem
func addPageNumberViewWithCount(count: Int) {
var containerViewX: CGFloat! = 0
let pageNumberViewDistance: CGFloat! = 10
let pageNumberViewPerSubview = 5
var numberOfSubview = count/pageNumberViewPerSubview
if(count % pageNumberViewPerSubview > 0){
numberOfSubview = numberOfSubview + 1
}
var pagesLeft = count
for i in 1...numberOfSubview {
var pageNumberViewX: CGFloat! = 0
let containerView : UIView = UIView(frame:CGRectMake(containerViewX,0,scrollView.frame.size.width,scrollView.frame.size.height))
if(pagesLeft < pageNumberViewPerSubview){
for k in 1...pagesLeft{
}
}
else{
for j in 1...pageNumberViewPerSubview{
let pageNumberView = UIView(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
pageNumberView.backgroundColor = UIColor.redColor()
pageNumberView.layer.cornerRadius = pageNumberView.frame.height / 2
pageNumberView.layer.masksToBounds = true
pageNumberView.clipsToBounds = true
// add number label
let label = UILabel(frame: CGRectMake(0, 0, 30, 30))
label.text = "\(i)"
label.textAlignment = .Center
pageNumberView.addSubview(label)
// update x for next view
pageNumberViewX = pageNumberView.frame.origin.x + pageNumberView.frame.width + pageNumberViewDistance
containerView.addSubview(pageNumberView)
}
containerViewX = containerViewX + scrollView.frame.size.width
// add view inside scrollview
scrollView.addSubview(containerView)
pagesLeft = pagesLeft - pageNumberViewPerSubview
}
if i == count {
scrollView.contentSize = CGSizeMake(numberOfSubview*scrollView.frame.size.width, 30)
}
}
}

Swift UILabel with horizontal lines on both sides

I am trying to create a UILabel that has 2 horizontal lines on the left and right side like this:
Does anyone know the best approach for doing this in Swift? The content text in the center will change so I want to make sure it can adapt. I'd really like to create some kind of reusable UIView class but I'm not sure where to start?
Thank you!
You can take two UIview of height 1 or 2 pixels of both side of the label. so it's look likes line!!
And you should set background color to black of that view!
Hope this will help :)
Take one UIView with height of 2. Set leading & Trailing according to Super View.
Now take one UILabel with background color white and put Vertically Center to line view.
Make both Center same.
Your work done.
For more help please refer below image.
You can use an extension on UILabel
public extension UILabel {
func drawLineOnBothSides(labelWidth: CGFloat, color: UIColor) {
let fontAttributes = [NSFontAttributeName: self.font]
let size = self.text?.size(attributes: fontAttributes)
let widthOfString = size!.width
let width = CGFloat(1)
let leftLine = UIView(frame: CGRect(x: 0, y: self.frame.height/2 - width/2, width: labelWidth/2 - widthOfString/2 - 10, height: width))
leftLine.backgroundColor = color
self.addSubview(leftLine)
let rightLine = UIView(frame: CGRect(x: labelWidth/2 + widthOfString/2 + 10, y: self.frame.height/2 - width/2, width: labelWidth/2 - widthOfString/2 - 10, height: width))
rightLine.backgroundColor = color
self.addSubview(rightLine)
}
}
This will add a horizontal line of width of 1.0 on the both side of your label. If you don't add text for your label, it will show two horizontal lines through center with some spaces in between them.
As others have mentioned, you can achieve this using three views:
Add a View to your scene to use as a container. I called this view "Lined Label Holder."
To that container, add two Views, one to produce the line on either side of the label.
Add the label in between the two views, and give it some text. Due to the "height = Test.height" constraint on the Lined Label Holder, The intrinsic height of this label is used to calculate the container's height.
The label is allowed to grow with added text and the lines will always start 5px away from the edges of the text and extended to the edges of the container, whose width can be set independently.
This image shows the required constraints:
Use one UIView with black background and height of 1px, set label background to white, align its text to center and align UILabel to center of UIView (there is no need for 2 views since label white background will cover UIView).
Not necessary 2 UIView's.
Take 1 UIView and give background black color.Add the constraints necessary with: height=2.
place 1 label on the center and give required constraints

Using UIStackView with a UITextView with dynamic height

I just found out about UIStackView, and I'm trying to see if it can finally make it easier to lay out expanding content.
Specifically, I want to do the following:
|-- UITextView ------------|
| some dynamic text here, | I want the text view's height to change
| it could be short, or it | depending on the height of the text
| could be tall | (set later, in code)
|-- UIView ----------------|
|xxxxxxxxxxxxxxxxxxxxxxxxxx| I want this view to expand to fill the
|xxxxxxxxxxxxxxxxxxxxxxxxxx| vertical space.
|xxxxxxxxxxxxxxxxxxxxxxxxxx|
|xxxxxxxxxxxxxxxxxxxxxxxxxx| It should start right below the
|xxxxxxxxxxxxxxxxxxxxxxxxxx| bottom of the UITextView
|xxxxxxxxxxxxxxxxxxxxxxxxxx|
|--------------------------|
I think I know how to do this with regular constraints. Is it possible to do with UIStackView? I might be understanding the purpose of UIStackView, but I would love to be able to create layouts like this more easily.
When I put both of these into a UIStackView, Interface builder says "Needs constraints for: Y position or height" for both views.
Programmatically it looks like this:
#IBOutlet private var stackView: UIStackView!
{
didSet {
stackView.addArrangedSubview(titleTextView)
stackView.addArrangedSubview(UIView())
}
}
lazy var titleTextView: UITextView =
{
let textView = UITextView()
textView.isScrollEnabled = false
return textView
}()
You don’t need anything else for the UITextView to expand automatically. This shouldn’t complain about ambiguities.
You can add a stackview then inside that add a textview, then set scrollable false, it will automaticaly handle your problem
Calculate the text height in textview add a height constraint on uitextview say 20 and when you calculate the height just change the height constraint.Give the below view constraint of top 0 from textview and you issue will be resolved.
Calculate height using this function
let height = string.boundingRectWithSize(CGSizeMake(CGFloat.max,textviewwidth), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: font!], context: nil).size.height
Try using an UILabel with lines = 0 instead of an UITextView.

How to evenly space two subviews around the center of UIStackView (iOS 9)?

How to evenly space two subviews around the centre of UIStackView in the vertical axis?
Currently both labels are next to the top and the bottom of the StackView. The stack view is the greyed area on the simulator screen shot.
How to make them evenly spaced?
|
|
[UILabel]
|
|
[UILabel]
|
|
I've tried different configurations, but I couldn't get the desired result.
Here is my current code:
let object = UIStackView()
object.translatesAutoresizingMaskIntoConstraints = false
object.alignment = UIStackViewAlignment.Center
object.axis = .Vertical
object.distribution = UIStackViewDistribution.EqualSpacing
Considering you have similar properties set to UIStackView (as mentioned in the question)
alignment -> Center
axis -> Vertical
distribution -> Equal Spacing
The solution can be achieved by introducing two empty views with height constraint set to 0 to both ends of UIStackView.
Explanation
On Adding two empty views of height 0, we are telling UIStackView that we have 4 views instead of 2.
Now UIStackView will add equal spacing between all the views and hence the output.
You can cross verify it by looking at the height of the UIStackView and the position of the labels.
Hope this helps !!!
In stackView attribute inspecter change spacing value from 0 to your preferred space value..

Resources