UIStackView - arranged views are overlapping - swift

I've been struggling for the past hours to add custom views to a UIStackView. The StackView is placed inside a UIScrollView, with constraints set to each margin of the ScrollView. Everything is design in a Storyboard.
Then in code I have the following for loop which should add my custom views to the stack:
for name in names {
let initialChildProfile = ChildProfileView.loadFromNibNamed(nibNamed: "ChildProfileView") as! ChildProfileView
initialChildProfile.frame = CGRect(x: 0, y: 0, width: childrenStackOutlet.frame.size.width, height: initialChildProfile.frame.size.height)
initialChildProfile.isUserInteractionEnabled = true
childrenStackOutlet.addArrangedSubview(initialChildProfile)
}
I've done this so many times before and everything went fine, but this time the custom views are overlapping each other. Only if I set the spacing to something larger than 0, I can actually see that is more than 1 view.
I tried setting the "translateAutoresizingMaskIntoConstraints" to false, I tried setting hardcoded values for the frame of the custom views, different constraints to the stack, even removed it from the scrollview. But nothing works.
PS I tried the few solutions I've seen online. Still nothing.
Thank you.

the problem is autolayout
for name in names {
let initialChildProfile = ChildProfileView.loadFromNibNamed(nibNamed: "ChildProfileView") as! ChildProfileView
initialChildProfile.translatesAutoresizingMaskIntoConstraints = false
initialChildProfile.widthAnchor.constraint(equalToConstant: childrenStackOutlet.frame.size.width).isActive = true
//initialChildProfile.heightAnchor.constraint(equalToConstant: self.view.frame.width - 50).isActive = true
initialChildProfile.isUserInteractionEnabled = true
childrenStackOutlet.addArrangedSubview(initialChildProfile)
}

Related

Determining when a view created programmatically did appear on screen

I have a textField which, when tapped, pops up a vertical stackView created programatically. The stackView is a child of the textField. textField.addSubview(stackView) places the stackView's origin at the textField's origin. I wish to move the stackView's origin up vertically by an amount equal to the height of the stackView. If I do:
func textFieldDidBeginEditing(_ textField: UITextField) {
stackView = UIStackView() // var stackView: UIStackView!
stackView.axis = .vertical
stackView.alignment = .leading
stackView.distribution = .fillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 0
stackView.backgroundColor = .white
stackView.layer.borderWidth = 1
stackView.layer.borderColor = UIColor.gray.cgColor
for title in titleList {
stackView.addArrangedSubview(createButton(title))
}
textField.addSubview(stackView)
stackView.transform = CGAffineTransform(translationX: 0, y: -stackView.frame.height)
}
It doesn't work. The stackView's origin remains at the textField's origin. But if I use a constant:
textField.addSubview(stackView)
stackView.transform = CGAffineTransform(translationX: 0, y: -144)
it works. I have confirmed through debugging that stackView.frame.height is 0 at the time the CGAffineTransform is performed. If I check its value after it has exited the function, I do see that its height is 144. That means the stackView hasn't actually appeared yet within the function.
Is there a way to catch when the programmatically-created stackView appears (sort of like the way viewDidAppear(_:) works) so that I can then set its position on the screen?
I don't think there is a canonical way to know when an individual view has appeared on screen. You might be able to hook into the draw method (which is hacky), but a stack view doesn't draw anything itself, so I don't think that works.
You could probably use UIViewController.viewDidLayoutSubviews, but you might need to add the stack view to the root view for that to work.
In any case, adding the UIStackView as a subview of the UITextField is bad practice. You probably want to add it to the parent view instead. And yes, using AutoLayout is the way to go here. Let the framework handle the layout, don't try to do it using transforms.

Shadow for collectionView is not coming properly in swift

I am made calendar with collectionview, for calendar i am using JTAppleCalendar here i need shadow for collectionview so i have added shadow to collectionview like below
override func viewDidLoad() {
super.viewDidLoad()
calendarView.layer.shadowColor = UIColor.black.cgColor
calendarView.layer.shadowOffset = CGSize(width: 0, height: 1)
calendarView.layer.shadowOpacity = 1
calendarView.layer.shadowRadius = 1.0
calendarView.layer.cornerRadius = 10
calendarView.clipsToBounds = false
calendarView.layer.masksToBounds = false
}
now the o/p: here if i scroll calendar for next month then i am getting un related dates(maybe next month dates) are coming right side.. how to remove that, and give perfect shadow to collectionview
if i didn't give shadow then i am not facing any problem while scrolling to next month.. only with shadow i am facing this issue
how to solve please do help.
calendarView.clipsToBounds = false is causing the views/cells outside current bounds to be visible as well.
You should add your calendarView as a subview in a container UIView instance and try applying the shadow code to that view.

Add layoutMargins to one element in a UIStackView

I would like to create a vertical stackview with 3 elements in it.
I want a bit more space only between the 2nd and the last element. So I thought about adding to the last element :
mylastelement.layoutMargins = UIEdgeInsets(top:30, left:0,bottom:0, right:0)
But the layoutmargins are not applied in my stackview. Is there any easy way to achieve that (Id like to avoid to modify the last element inner height).
EDIT : I just tried to increase 2nd element height (+50) within its frame by doing :
my2ndElementLabel.sizeToFit()
my2ndElementLabel.frame = CGRect(x:my2ndElementLabel.frame.origin.x,y:lmy2ndElementLabel.frame.origin.y,
width:my2ndElementLabel.frame.width, height:my2ndElementLabel.frame.height + 50)
but it has no effect.
EDIT2 : I tried to add a random view to my UIStackView, but the the view is just ignored ! May have missed something in understanding how UIKit work ?... :
let v = UIView(frame:CGRect(x:0,y:0,width:100,height:400))
v.backgroundColor = .red
myStackView.addArrangedSubview(v)
//...
Here is an extension I made that helps to achieve fast such margins :
extension UIStackView {
func addArrangedSubview(_ v:UIView, withMargin m:UIEdgeInsets )
{
let containerForMargin = UIView()
containerForMargin.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: containerForMargin.topAnchor, constant:m.top ),
v.bottomAnchor.constraint(equalTo: containerForMargin.bottomAnchor, constant: m.bottom ),
v.leftAnchor.constraint(equalTo: containerForMargin.leftAnchor, constant: m.left),
v.rightAnchor.constraint(equalTo: containerForMargin.rightAnchor, constant: m.right)
])
addArrangedSubview(containerForMargin)
}
}
What you could do is set a custom spacing between the second and third element.
myStackView.setCustomSpacing(30.0, after: my2ndElementLabel)
In the same general vein, you can constrain the top (or bottom) anchor of your view relative to the corresponding edge of any view in which it's embedded. What's ugly being somewhat a matter of taste, I find autolayout constraints easy to use and easy to reason about.
A simple example from Mac OS rather than iOS:
let button = ControlFactory.labeledButton("Filter")
addSubview(button)
button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20).isActive = true
button.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
This particular code lives in the view initializer, and positions a button in the middle of a view, 20 points up from the bottom.
I found myself : It looks like UIStackView doesn't work at all with old sizing system (with .frame). It seems you have to constraint height and width, and StackView will constraint left/top/right/bottom position for you when you add the arrangedSubview.
My second view was a label : I wanted a margin of 40, under the text. So i first computed the label height into its .frame property, and constraint the height at frame.height + 40(= my margin)
labelDesc.sizeToFit()
labelDesc.heightAnchor.constraint(equalToConstant:40).isActive = true
I find my own solution utterly ugly though. I'm sure UIKit provide a better way to achieve such a simple goal, without having to make these kind of DIY solutions. So please if you're used to work with UIKit, tell me if there is any better solution.
Consider adding a "margin" by inserting a correctly-sized UIView within the Stack View as needed.
If you need a 40px margin between 2 specific elements... add a UIView with a height constraint of 40px. Assign a clearColor background to make it invisible.
You can add IBOutlets to this view and hide it as you would any other item in the Stack View.

How to add NSTextFields to NSScrollView

I want to add multiple NSTextFields to NSScrollView dynamically.
Code:
func addLabel(data: String)
{
let offset = labelCount*20
let label = NSTextField(frame: NSMakeRect(0, CGFloat(offset), 200, 20))
label.stringValue = data
scrollView.documentView?.addSubview(label)
labelCount += 1
// scrollView is IBOutlet
// labelCount is var to keep record of number of labels
}
Problem:
It adds the NSTextFields correctly to the NSScrollView but they are only added till the visible part is available. After the visible part of NSScrollView is full, no more NSTextFields are added to it.
I need to add NSTextFields even though the visible part is full and scroll to find others.
How can I do it?
It's something like the following.
scrollView.documentView?.setFrameSize(CGSize(width: CGFloat(labelCount) * 200, height: 20))

Setting constraints on UITableView inside UIView

I have a UIViewController that has some base controls and in the center has a UIView (as container of swappable ui controls). I swap out the UI controls in this center UIView (container) depending on what the user is trying to do. All of the UI controls that go in to the UIView container are defined programmatically and I use programatic constraints to place them inside the UIView container.
This works fine for all of the sets of UI controls I have done so far. Now I am adding a set of controls to the UIView container that includes a UITableView
I cant figure out how to get the TableView to show up inside the UIView programatically. I can define say a button and label and run the app and see the container with the button and the label. If I add the UITableView as below then the container just does not show up at all.
// tableView
tableView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: inputsContainerView.bottomAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
tableView.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: 1/2).isActive = true
prior to the above code I have already added all of the needed controls to the container subview ...
// Add controls to the view
inputsContainerView.addSubview(listTextView)
inputsContainerView.addSubview(listImageButton)
inputsContainerView.addSubview(listImageClear)
inputsContainerView.addSubview(tableView)
If I leave off the tableview then the container shows up with the other three fields. If I add the tableview then the container and all the other three controls are gone.
How do I add the tableView to the UIView and have it show up?
Here is how I defined the UITable view
let tableView: UITableView = {
let tv = UITableView()
return tv
}()
as a compare, when I define others controls like this they show up fine after adding to the subview and setting the constraints programatically
e.g.
// DEFINE
let listTextView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = ""
textView.textColor = defaultTextColor
textView.font = subtitleFont
textView.layer.borderColor = UIColor.black.cgColor
textView.layer.borderWidth = 1
textView.textAlignment = NSTextAlignment.left
return textView
}()
then later
// Place - with constraints
// listTextView
listTextView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor, constant: padFromLeft).isActive = true
listTextView.topAnchor.constraint(equalTo: inputsContainerView.topAnchor, constant: 10).isActive = true
listTextView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor, multiplier: 9/16).isActive = true
listTextView.heightAnchor.constraint(equalTo: inputsContainerView.widthAnchor, multiplier: 5/16).isActive = true
Just added my comment as detailed answer, so others can see the solution faster and benefit from it.
So taken from the apple documentation, to set your own constraints programmatically, you need to set translatesAutoresizingMaskIntoConstraints to false:
Note that the autoresizing mask constraints fully specify the view’s size and position; therefore, you cannot add additional constraints to modify this size or position without introducing conflicts. If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false, and then provide a non ambiguous, nonconflicting set of constraints for the view.
So in your case you miss to set it for your table view, when you define it. Just add this line to it:
tv.translatesAutoresizingMaskIntoConstraints = false