Load view from XIB as a subview of a scrollview - ios

I'm still a SO and Swift newbie, so please, be patient and feel free to skip this question :-)
In the body of a XIB's awakeFromNib, I want to load some views as subviews of a UIScrollView (basically, the XIB contains a scrollview, a label and a button).
The scrollview perfectly works if in a loop I load views I create on the fly, eg.
let customView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 150))
customView.frame = CGRect(x: i*300 , y: 0, width: 300, height: 150)
customView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(customView)
But I have a different goal.
In another XIB I have an image view and a stackview with some labels. This XIB is connected in the storyboard to a class SingleEvent that extends UIView.
I want to do the following:
use the XIB as a sort of "blueprint" and load the same view multiple times in my scrollview;
pass to any instance some data;
Is this possible?
I tried to load the content of the XIB this way:
let customView = Bundle.main.loadNibNamed("SingleEvent", owner: self, options: nil)?.first as? SingleEvent
and this way:
let customView = SingleEvent()
The first one makes the app crash, while the second causes no issue, but I can't see any effect (it doesn't load anything).
The content of my latest SingleEvent is the following:
import UIKit
class SingleEvent: UIView {
#IBOutlet weak var label:UILabel!
#IBOutlet weak var imageView:UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib()
}
func loadViewFromNib() -> UIView {
let myView = Bundle.main.loadNibNamed("SingleEvent", owner: self, options: nil)?.first as! UIView
return myView
}
}
Thanks in advance, any help is appreciated :-)

There are a number of approaches to loading custom views (classes) from xibs. You may find this method a bit easier.
First, create your xib like this:
Note that the Class of File's Owner is the default (NSObject).
Instead, assign your custom class to the "root" view in your xib:
Now, our entire custom view class looks like this:
class SingleEvent: UIView {
#IBOutlet var topLabel: UILabel!
#IBOutlet var middleLabel: UILabel!
#IBOutlet var bottomLabel: UILabel!
#IBOutlet var imageView: UIImageView!
}
And, instead of putting loadNibNamed(...) inside our custom class, we create a UIView extension:
extension UIView {
class func fromNib<T: UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
To load and use our custom class, we can do this:
class FromXIBViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create an instance of SingleEvent from its xib/nib
let v = UIView.fromNib() as SingleEvent
// we're going to use auto-layout & constraints
v.translatesAutoresizingMaskIntoConstraints = false
// set the text of the labels
v.topLabel?.text = "Top Label"
v.middleLabel?.text = "Middle Label"
v.bottomLabel?.text = "Bottom Label"
// set the image
v.imageView.image = UIImage(named: "myImage")
// add the SingleEvent view
view.addSubview(v)
// constrain it 200 x 200, centered X & Y
NSLayoutConstraint.activate([
v.widthAnchor.constraint(equalToConstant: 200.0),
v.heightAnchor.constraint(equalToConstant: 200.0),
v.centerXAnchor.constraint(equalTo: view.centerXAnchor),
v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
With a result of:
And... here is an example of loading 10 instances of SingleEvent view and adding them to a vertical scroll view:
class FromXIBViewController: UIViewController {
var theScrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
var theStackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.alignment = .fill
v.distribution = .fill
v.spacing = 20.0
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the scroll view to the view
view.addSubview(theScrollView)
// constrain it 40-pts on each side
NSLayoutConstraint.activate([
theScrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
theScrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40.0),
theScrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 40.0),
theScrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40.0),
])
// add a stack view to the scroll view
theScrollView.addSubview(theStackView)
// constrain it 20-pts on each side
NSLayoutConstraint.activate([
theStackView.topAnchor.constraint(equalTo: theScrollView.topAnchor, constant: 20.0),
theStackView.bottomAnchor.constraint(equalTo: theScrollView.bottomAnchor, constant: -20.0),
theStackView.leadingAnchor.constraint(equalTo: theScrollView.leadingAnchor, constant: 20.0),
theStackView.trailingAnchor.constraint(equalTo: theScrollView.trailingAnchor, constant: -20.0),
// stackView width = scrollView width -40 (20-pts padding on left & right
theStackView.widthAnchor.constraint(equalTo: theScrollView.widthAnchor, constant: -40.0),
])
for i in 0..<10 {
// create an instance of SingleEvent from its xib/nib
let v = UIView.fromNib() as SingleEvent
// we're going to use auto-layout & constraints
v.translatesAutoresizingMaskIntoConstraints = false
// set the text of the labels
v.topLabel?.text = "Top Label: \(i)"
v.middleLabel?.text = "Middle Label: \(i)"
v.bottomLabel?.text = "Bottom Label: \(i)"
// set the image (assuming we have images named myImage0 thru myImage9
v.imageView.image = UIImage(named: "myImage\(i)")
theStackView.addArrangedSubview(v)
}
}
}
Result:

Ok, I see. The problem probably in fact that loadViewFromNib function return UIView from xib, but you doesn't use it any way.
Let's try this way:
1) Make your loadViewFromNib function static
// Return our SingleEvent instance here
static func loadViewFromNib() -> SingleEvent {
let myView = Bundle.main.loadNibNamed("SingleEvent", owner: self, options: nil)?.first as! SingleEvent
return myView
}
2) Remove all inits in SingleEvent class
3) Init it in needed place like this:
let customView = SingleView.loadViewFromNib()
To pass data inside view you can create new function in SingleView class:
func configureView(with dataModel:DataModel) {
//Set data to IBOutlets here
}
And use it from outside like this:
let customView = SingleView.loadViewFromNib()
let dataModel = DataModel()
customView.configureView(with: dataModel)

Related

How to put UIView inside UIScrollView in Swift?

I am trying to add a UIView into a UIScrollView without storyboard. I made some code with the given two files(CalcTypeView.siwft and CalcTypeViewController.swift) as below. However, as shown in the screenshot image, I can see the UIScrollView(gray color) while UIView(red color) does not appear on the screen. What should I do more with these code to make UIView appear? (I've already found many example code using single UIViewController, but what I want is UIView + UIViewController form to maintain MVC pattern)
1. CalcTypeView.swift
import UIKit
final class CalcTypeView: UIView {
private let scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .gray
view.showsVerticalScrollIndicator = true
return view
}()
private let contentView1: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
view.clipsToBounds = true
view.layer.cornerRadius = 10
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupScrollView()
setupContentView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupScrollView() {
self.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
scrollView.widthAnchor.constraint(equalTo: self.widthAnchor),
scrollView.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor),
])
}
private func setupContentView() {
scrollView.addSubview(contentView1)
NSLayoutConstraint.activate([
contentView1.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 20),
contentView1.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -20),
contentView1.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 20),
contentView1.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -20),
])
}
}
2. CalcTypeViewController.swift
import UIKit
final class CalcTypeViewController: UIViewController {
private let calcTypeView = CalcTypeView()
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
setupView()
}
private func setupNavBar() {
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithOpaqueBackground()
navigationBarAppearance.shadowColor = .clear
navigationController?.navigationBar.standardAppearance = navigationBarAppearance
navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearance
navigationController?.navigationBar.tintColor = Constant.ColorSetting.themeColor
navigationController?.navigationBar.prefersLargeTitles = false
navigationController?.setNeedsStatusBarAppearanceUpdate()
navigationController?.navigationBar.isTranslucent = false
navigationItem.scrollEdgeAppearance = navigationBarAppearance
navigationItem.standardAppearance = navigationBarAppearance
navigationItem.compactAppearance = navigationBarAppearance
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "bookmark.fill"), style: .plain, target: self, action: #selector(addButtonTapped))
navigationItem.rightBarButtonItem?.tintColor = Constant.ColorSetting.themeColor
navigationItem.title = Constant.MenuSetting.menuName2
self.extendedLayoutIncludesOpaqueBars = true
}
override func loadView() {
view = calcTypeView
}
private func setupView() {
view.backgroundColor = .systemBackground
}
#objc private func addButtonTapped() {
let bookmarkVC = BookmarkViewController()
navigationController?.pushViewController(bookmarkVC, animated: true)
}
}
My Screenshot
A scroll view's contentLayoutGuide defines the size of the scrollable area of the scroll view. The default size is 0,0.
In your code your contentView1 has no intrinsic size. It simply has a default size of 0,0. So your constraints are telling the scroll view to make its contentLayoutGuide to be 40,40 (based on the 20 and -20 constants) and leave the contentView1 size as 0,0.
If you setup contentView1 with specific width and height constraints then the scroll view's content size would be correct so that contentView1 would scroll within the scroll view.
A better example might be to add a UIStackView with a bunch of labels. Since the stack view will have an intrinsic size based on its content and setup, the contentLayoutGuide of the scroll view will fit around the stack view's intrinsic size.

Self sizing table view cells based on ui stack view content

i want to create cells with stack view in it. For different cells there would be different amount of arranged subviews in stack view.
Currently i create something like this but self sizing isn't working.
Table view initialization:
let tableView: UITableView = {
let tableView = UITableView()
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200
return tableView
}()
Table view cell:
import UIKit
final class CustomTableViewCell: UITableViewCell {
private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
stackView.distribution = .equalSpacing
stackView.alignment = .fill
return stackView
}()
// initializations...
private func setup() {
backgroundColor = .green
addSubviewsWithConstraints([stackView])
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
stackView.addArrangedSubviews([CustomView(), CustomView()])
// custom views will be added later depends on model
}
}
Custom view:
final class CustomView: UIView {
private let iconImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart.fill")
return imageView
}()
//MARK: - Override
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
private func setup() {
addSubviewsWithConstraints([iconImageView])
NSLayoutConstraint.activate([
iconImageView.topAnchor.constraint(equalTo: topAnchor, constant: 6),
iconImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 6),
iconImageView.heightAnchor.constraint(equalToConstant: 80),
iconImageView.widthAnchor.constraint(equalToConstant: 80)
])
}
}
To add subviews i'm using custom extension to uiview:
func addSubviewWithConstraints(_ view: UIView) {
view.translatesAutoresizingMaskIntoConstraints = false
addSubview(view)
}
func addSubviewsWithConstraints(_ views: [UIView]) {
views.forEach {
addSubviewWithConstraints($0)
}
}
Result looks like this (using 2 cells with 2 custom views in stack views):
Your addSubviewsWithConstraint is adding views as a subview of the cell not as a subview of the contentView. Cells use the contentView for self sizing. As addSubviewsWithConstraint is an extension of UIView you should be able to do the following anywhere you want to add a subview to a cell.
contentView.addSubviewWithConstraints(...)
Just another thing that may be useful is that you can shorten your functions down by using variadic arguments like so:
func addSubviewWithConstraints(_ views: UIView...) {
views.forEach {
addSubviewWithConstraints($0)
}
}
Usage
addSubviewWithConstraints(mySubview)
addSubviewWithConstraints(mySubview, mySubview2, mySubview3)

Swift UIView Subviews not rounding corners in Custom UIView Subclass

Greetings stack overflow.
I am trying to build a "bullseye" type view, using coloured subviews and the corner radius. The problem I have is, only my first subview's corners are getting rounded and the inner views are still squares. The black view is a subview of my custom view. The red view is it's subview, and they yellow view the subview of that. Pretty simple hierarchy.
The result looks like this:
I add the views and set their constraints manually. My test app just has the ThreeCircleView dead center of a view controller with the X,Y centered and the width, height constant. I do the actual rounding of the corners in didLayoutSubViews because the size of the view might change, so the corners would have to be resized.
I wrote a test view to isolate this, here it is
class ThreeCircleView: UIView {
var outerCircle: UIView = UIView()
var middleCircle: UIView = UIView()
var innerCircle: UIView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
addSubViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
translatesAutoresizingMaskIntoConstraints = false
addSubViews()
}
func addSubViews() {
outerCircle.backgroundColor = .black
middleCircle.backgroundColor = .red
innerCircle.backgroundColor = .yellow
self.addSubview(outerCircle)
outerCircle.addSubview(middleCircle)
middleCircle.addSubview(innerCircle)
let outerCenterY = outerCircle.centerYAnchor.constraint(equalTo: self.centerYAnchor)
let outerCenterX = outerCircle.centerXAnchor.constraint(equalTo: self.centerXAnchor)
let outerCenterWidth = outerCircle.widthAnchor.constraint(equalTo: self.widthAnchor, constant: -50.0 )
let outerCenterHeight = outerCircle.heightAnchor.constraint(equalTo: self.heightAnchor, constant: -50.0 )
outerCircle.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([outerCenterY,outerCenterX,outerCenterWidth,outerCenterHeight])
self.setNeedsLayout()
let middleCenterY = middleCircle.centerYAnchor.constraint(equalTo: self.centerYAnchor)
let middleCenterX = middleCircle.centerXAnchor.constraint(equalTo: self.centerXAnchor)
let middleCenterWidth = middleCircle.widthAnchor.constraint(equalTo: self.widthAnchor, constant: -100.0 )
let middleCenterHeight = middleCircle.heightAnchor.constraint(equalTo: self.heightAnchor, constant: -100.0 )
middleCircle.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([middleCenterY,middleCenterX,middleCenterWidth,middleCenterHeight])
let innerCenterY = innerCircle.centerYAnchor.constraint(equalTo: self.centerYAnchor)
let innerCenterX = innerCircle.centerXAnchor.constraint(equalTo: self.centerXAnchor)
let innerCenterWidth = innerCircle.widthAnchor.constraint(equalTo: self.widthAnchor, constant: -150.0 )
let innerCenterHeight = innerCircle.heightAnchor.constraint(equalTo: self.heightAnchor, constant: -150.0 )
innerCircle.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([innerCenterY,innerCenterX,innerCenterWidth,innerCenterHeight])
}
func makeCircle(v:UIView) {
v.layer.cornerRadius = v.frame.size.width * 0.50
v.clipsToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
makeCircle(v: outerCircle)
makeCircle(v: middleCircle)
makeCircle(v: innerCircle)
}
}
An easy way to make it look as expected is to add layoutIfNeeded() call inside your makeCircle(v:UIView) method. This will make you sure that all views' frames are updated correctly before applying visual changes:
func makeCircle(v:UIView) {
v.layoutIfNeeded()
v.layer.cornerRadius = v.frame.size.width * 0.50
v.clipsToBounds = true
}

Dynamic View in iOS inside UITableViewCell

I'm creating following view (display under In Transit) which are used to maintain my product status. See below image.
I want to create this view in UITableViewCell, I have tried by placing fixed height/width view (Circle View with different color) and horizontal gray line view and it's work fine for fixed spot point. I'm able to create this for fixed view using storyboard.
My Problem is, these are dynamic spot point view. Currently it's 4, but it can be vary based on status available in API response.
Anyone have idea? How to achieve this status spot dynamic view?.
You can achieve your thing using UICollectionView inside UITableViewCell.
First create following design for collection view cell. This collection view added inside table view cell.
CollectionViewCell:
See Constraints:
Regarding spotview and circleview you can recognise by constraints and view. So don't confuse therem otherwise all naming convention are available based on view's priority.
Now you need to take outlet of collection view inside UITableViewCell's subclass whatever you made and collection view cell's subview to UICollectionViewCell's subclass.
UITableViewCell:
class CTrackOrderInTransitTVC: UITableViewCell {
#IBOutlet weak var transitView : UIView!
#IBOutlet weak var cvTransit : UICollectionView!
var arrColors: [UIColor] = [.blue, .yellow, .green, .green]
override func awakeFromNib() {
super.awakeFromNib()
}
}
Now add following code in your collection view cell subclass, It's contains outlets of your subViews of collection view cell:
class CTrackOrderInTransitCVC: UICollectionViewCell {
#IBOutlet weak var leftView : UIView!
#IBOutlet weak var rightView : UIView!
#IBOutlet weak var spotView : UIView!
#IBOutlet weak var circleView : UIView!
}
Thereafter, you have to implemented table view datasource method load your collection view cell inside your table.
See the following code:
extension YourViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//------------------------------------------------------------------------------
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CTrackOrderInTransitTVC", for: indexPath) as! CTrackOrderInTransitTVC
// Reload collection view to update sub views
cell.cvTransit.reloadData()
return cell
}
}
I hope this will help you.
You can do this with a UIStackView using "spacer" views.
Add a clear UIView between each "dot" view, and constrain the width of each "spacer" view equal to the first "spacer" view.
Add a UIStackView, constrain its width and centerY to the tracking line, and set the properties to:
Axis: Horizontal
Alignment: Fill
Distribution: Fill
Spacing: 0
Your code to add the "dots" will be something like this:
for i in 0..<numberOfDots {
create a dot view
add it to the stackView using .addArrangedSubview()
one fewer spacers than dots (e.g. 4 dots have a spacer between each = 3 spacers), so,
if this is NOT the last dot,
create a spacer view
add it to the stackView
}
Keep track of the spacer views, and set their width constraints each equal to the first spacer view.
Here is some starter code which may help you get going. The comments should make it clear what's being done. Everything is being done in code (no #IBOutlets) so you should be able to run it by adding a view controller in storyboard and assigning its custom class to DotsViewController. It adds the view as a "normal" subview... but of course can also be added as a subview of a cell.
class DotView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.size.height * 0.5
}
}
class TrackingLineView: UIView {
var theTrackingLine: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return v
}()
var theStack: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .horizontal
v.alignment = .fill
v.distribution = .fill
v.spacing = 0
return v
}()
var trackingDot: DotView = {
let v = DotView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
return v
}()
let dotWidth = CGFloat(6)
let trackingDotWidth = CGFloat(20)
var trackingDotCenterX = NSLayoutConstraint()
var dotViews = [DotView]()
var trackingPosition: Int = 0 {
didSet {
let theDot = dotViews[trackingPosition]
trackingDotCenterX.isActive = false
trackingDotCenterX = trackingDot.centerXAnchor.constraint(equalTo: theDot.centerXAnchor, constant: 0.0)
trackingDotCenterX.isActive = true
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
// add the tracking line
addSubview(theTrackingLine)
// add the "big" tracking dot
addSubview(trackingDot)
// add the stack view that will hold the small dots (and spacers)
addSubview(theStack)
// the "big" tracking dot will be positioned behind a small dot, so we need to
// keep a reference to its centerXAnchor constraint
trackingDotCenterX = trackingDot.centerXAnchor.constraint(equalTo: theTrackingLine.centerXAnchor, constant: 0.0)
NSLayoutConstraint.activate([
theTrackingLine.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0.0),
theTrackingLine.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0.0),
theTrackingLine.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0, constant: -20.0),
theTrackingLine.heightAnchor.constraint(equalToConstant: 2.0),
theStack.centerXAnchor.constraint(equalTo: theTrackingLine.centerXAnchor, constant: 0.0),
theStack.centerYAnchor.constraint(equalTo: theTrackingLine.centerYAnchor, constant: 0.0),
theStack.widthAnchor.constraint(equalTo: theTrackingLine.widthAnchor, multiplier: 1.0, constant: 0.0),
trackingDotCenterX,
trackingDot.widthAnchor.constraint(equalToConstant: trackingDotWidth),
trackingDot.heightAnchor.constraint(equalTo: trackingDot.widthAnchor, multiplier: 1.0),
trackingDot.centerYAnchor.constraint(equalTo: theTrackingLine.centerYAnchor, constant: 0.0),
])
}
func setDots(with colors: [UIColor]) -> Void {
// remove any previous dots and spacers
// (in case we're changing the number of dots after creating the view)
theStack.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
// reset the array of dot views
// (in case we're changing the number of dots after creating the view)
dotViews = [DotView]()
// we're going to set all spacer views to equal widths, so use
// this var to hold a reference to the first one we create
var firstSpacer: UIView?
colors.forEach {
c in
// create a DotView
let v = DotView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = c
// add to array so we can reference it later
dotViews.append(v)
// add it to the stack view
theStack.addArrangedSubview(v)
// dots are round (equal width to height)
NSLayoutConstraint.activate([
v.widthAnchor.constraint(equalToConstant: dotWidth),
v.heightAnchor.constraint(equalTo: v.widthAnchor, multiplier: 1.0),
])
// we use 1 fewer spacers than dots, so if this is not the last dot
if c != colors.last {
// create a spacer (clear view)
let s = UIView()
s.translatesAutoresizingMaskIntoConstraints = false
s.backgroundColor = .clear
// add it to the stack view
theStack.addArrangedSubview(s)
if firstSpacer == nil {
firstSpacer = s
} else {
// we know it's not nil, but we have to unwrap it anyway
if let fs = firstSpacer {
NSLayoutConstraint.activate([
s.widthAnchor.constraint(equalTo: fs.widthAnchor, multiplier: 1.0),
])
}
}
}
}
}
}
class DotsViewController: UIViewController {
var theButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .red
v.setTitle("Move Tracking Dot", for: .normal)
v.setTitleColor(.white, for: .normal)
return v
}()
var theTrackingLineView: TrackingLineView = {
let v = TrackingLineView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
return v
}()
var trackingDots: [UIColor] = [
.yellow,
.red,
.orange,
.green,
.purple,
]
var currentTrackingPosition = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 1.0, green: 0.8, blue: 0.5, alpha: 1.0)
view.addSubview(theTrackingLineView)
NSLayoutConstraint.activate([
theTrackingLineView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
theTrackingLineView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0),
theTrackingLineView.heightAnchor.constraint(equalToConstant: 100.0),
theTrackingLineView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9),
])
theTrackingLineView.setDots(with: trackingDots)
theTrackingLineView.trackingPosition = currentTrackingPosition
// add a button so we can move the tracking dot
view.addSubview(theButton)
NSLayoutConstraint.activate([
theButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 40.0),
theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
theButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: Any) -> Void {
// if we're at the last dot, reset to 0
if currentTrackingPosition < trackingDots.count - 1 {
currentTrackingPosition += 1
} else {
currentTrackingPosition = 0
}
theTrackingLineView.trackingPosition = currentTrackingPosition
UIView.animate(withDuration: 0.25, animations: {
self.view.layoutIfNeeded()
})
}
}
The result:
i recommend you use a collection view inside table cell, so that way you can define the position with a simple validation

How to add xib subviews with dynamic height to UIView

It should be easy to do and it's very easy in android(with LinearLayout) but I can't figure out how to do it in ios.
I have UIView into which I want to add some dynamic height subviews. Subview is loaded from xib file and contains 2 UILabels with dynamic height.
class Subview: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var desc: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
fileprivate func commonInit() {
Bundle.main.loadNibNamed("Subview", owner: self, options: nil)
addSubview(contentView)
self.contentView.frame = self.bounds
self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
and subview is added like this
var containerView: UIView
func setupView() {
let subview1 = Subview()
subview1.title = "Some title"
subview1.desc = "Some very long description"
self.containerView.addSubview(subview1)
}
Issue is that in this case, subview is not displayed.
If I add
subview1.frame = CGSize(width: self.bounds.width, height: 100)
subview is displayed but height 100 is not correct
If I try to calculate subview size
let measuredSize = subview1.sizeThatFits(CGSize(width: self.bounds.width, height: CGFloat(MAXFLOAT)))
measured width and size is 0.
Any idea what I am doing wrong here? Thanks!
Edit: This is my subview xib file with constraints
You can easily accomplish this by
1- hooking the constraints properly in the xib file from top to bottom
2- theView.translatesAutoresizingMaskIntoConstraints = false
3- set leading , trailing , top constraints ( No height ) , for eample this is a simple xib with one label
When adding it to self.view by
let vv = (Bundle.main.loadNibNamed("repairView", owner: self, options: nil))?[0] as! repairView;
vv.translatesAutoresizingMaskIntoConstraints = false
vv.aedlb.text = "dsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsvvvvdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsvvvvdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsvvvvdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsvvvvdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjdsdsjhdsjhdsjhsdhjdsjhesisbeudjbdsjbdsjds"
view.addSubview(vv)
NSLayoutConstraint.activate([
vv.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
vv.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
vv.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0),
])
The result

Resources