Swift SnapKit dynamic height issue - swift

i have used snapKit on DispatchQueue.main.async to set constraints programmatically. But i noticed that main.async sometimes causes freeze UI and i change method to use it. Now i have problem to use dynamic height, old method cause error when i try to make dynamic height depends of content...
OLD METHOD (innerView - is subview of view)
DispatchQueue.main.async {
view.snp.makeConstraints({ (make) in
make.top.equalToSuperview().inset(45)
make.left.right.equalToSuperview().inset(12)
make.bottom.equalTo(self.innerView.snp.bottom).offset(12)
})
}
//After i add it like superView.addSubView(view)
//superView contains view, and view(dynamic height) contains innerView
NEW METHOD
superView.addSubview(view)
view.snp.makeConstraints({ (make) in
make.top.equalToSuperview().inset(45)
make.left.right.equalToSuperview().inset(12)
make.bottom.equalTo(self.innerView.snp.bottom).offset(12)
})

You have to add both before setting the constraints
superView.addSubview(view)
superView.addSubview(innerView) // or view.addSubview(innerView) if it's nested UI
view.snp.makeConstraints({ (make) in
make.top.equalToSuperview().inset(45)
make.left.right.equalToSuperview().inset(12)
make.bottom.equalTo(self.innerView.snp.bottom).offset(12)
})

I found issue, just in case someone will need it... add make.bottom.equalTo(self.innerView.snp.bottom).offset(12), after you add all superview constraints and it will work!

Related

Bad layout constraints when adding accessory view to Open/Save dialog

I'm trying to add a simple NSView with a checkbox as an accessory view to an NSOpenPanel, but when I run my program, I get an error saying The Open/Save panel was supplied an accessory view with bad layout constraints, resulting in a view that is zero [height/width]. Here are the constraints I've added to the view:
And here are the constraints for the checkbox:
Here's the code for creating the NSOpenPanel:
let dlgOpenSounds: NSOpenPanel = NSOpenPanel()
let optionsView = BatchAddOptionsView()
dlgOpenSounds.accessoryView = optionsView
dlgOpenSounds.accessoryView?.awakeFromNib()
let result = dlgOpenSounds.runModal()
if result == .OK {
// do stuff
}
Anyone know what I'm doing wrong?
I ran into the same issue with a similar arrangement created in code, and finally worked it out. My implementation is handled in a custom NSView subclass, which I then add as the NSOpenPanel's .accessoryView from the view controller where I display the panel.
private func setup() {
hiddenFilesCheckbox = NSButton(checkboxWithTitle: "Show Hidden Files", target: self, action: #selector(hiddenFilesCheckboxValueChanged))
guard let checkbox = hiddenFilesCheckbox else {
os_log("Hidden files checkbox is nil")
return
}
addSubview(checkbox)
checkbox.translatesAutoresizingMaskIntoConstraints = false
checkbox.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 12).isActive = true
checkbox.topAnchor.constraint(equalTo: self.topAnchor, constant: 12).isActive = true
self.heightAnchor.constraint(greaterThanOrEqualToConstant: frame.height).isActive = true
self.widthAnchor.constraint(greaterThanOrEqualToConstant: frame.width).isActive = true
}
"hiddenFilesCheckbox" is declared as a property of my custom NSView subclass. I played around with some other hard-coded values for the constants, but these worked best in my tests. I pass in the openPanel to the subclass's initializer to use its frame to set the accessoryView's width. I used a hard-code value of 40 for the height in the initializer that isn't included here. After setting up the accessory view with these constraints, the warnings stopped appearing and the accessory view appears as desired/expected.
Try setting up the view like this (Xcode 10.1). First make sure that AutoLayout on the view is not selected. Then:
Change the view width and height to whatever is appropriate (I'm using a 'small' control size)
Setup the checkbox similar to:
Again, adjust the width and height as necessary. No other constraints should be added.
Note that if you save and reuse the accessory view in multiple panel.beginModalSheet() calls, you'll get a console warning because the previous beginModalSheet() added layout constraints.

Prefer Large Titles and RefreshControl not working well

I am using this tutorial to implement a pull-to-refresh behavior with the RefreshControl. I am using a Navigation Bar. When using normal titles everything works good. But, when using "Prefer big titles" it doesn't work correctly as you can see in the following videos. Anyone knows why? The only change between videos is the storyboard check on "Prefer Large Titles".
I'm having the same problem, and none of the other answers worked for me.
I realised that changing the table view top constraint from the safe area to the superview fixed that strange spinning bug.
Also, make sure the constant value for this constraint is 0 🤯.
At the end what worked for me was:
In order to fix the RefreshControl progress bar disappearing bug with large titles:
self.extendedLayoutIncludesOpaqueBars = true
In order to fix the list offset after refreshcontrol.endRefreshing():
let top = self.tableView.adjustedContentInset.top
let y = self.refreshControl!.frame.maxY + top
self.tableView.setContentOffset(CGPoint(x: 0, y: -y), animated:true)
If you were using tableView.tableHeaderView = refreshControl or tableView.addSubView(refreshControl) you should try using tableView.refreshControl = refreshControl
It seems there are a lot of different causes that could make this happen, for me I had a TableView embedded within a ViewController. I set the top layout guide of the tableview to the superview with 0. After all of that still nothing until I wrapped my RefreshControl end editing in a delayed block:
DispatchQueue.main.async {
if self.refreshControl.isRefreshing {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
self.refreshControl.endRefreshing()
})
}
}
The only working solution for me is combining Bruno's suggestion with this line of code:
tableView.contentInsetAdjustmentBehavior = .always
I've faced the same problem. Call refreshControl endRefreshing before calling further API.
refreshControl.addTarget(controller, action: #selector(refreshData(_:)), for: .valueChanged)
#objc func refreshData(_ refreshControl: UIRefreshControl) {
refreshControl.endRefreshing()
self.model.loadAPICall {
self.tableView.reloadData()
}
}
The only solution that worked for me using XIBs was Bruno's one:
https://stackoverflow.com/a/54629641/2178888
However I did not want to use a XIB. I struggled a lot trying to make this work by code using AutoLayout.
I finally found a solution that works:
override func loadView() {
super.loadView()
let tableView = UITableView()
//configure tableView
self.view = tableView
}
I had this issue too, and i fixed it by embedded my scrollView (or tableView \ collectionView) inside stackView, and it's important that this stackView's top constraint will not be attached to the safeArea view (all the other constraints can). the top constraint should be connect to it's superview or to other view.
I was facing the same issue for very long, the only working solution for me was adding refresh control to the background view of tableview.
tableView.backgroundView = refreshControl
Short Answer
I fixed this by delaying calling to API until my collection view ends decelerating
Long Answer
I notice that the issue happens when refresh control ends refreshing while the collection view is still moving up to its original position. Therefore, I delay making API call until my collection view stops moving a.k.a ends decelerating. Here's a step by step:
Follow Bruno's suggestion
If you set your navigation bar's translucent value to false (navigationBar.isTranslucent = false), then you will have to set extendedLayoutIncludesOpaqueBars = true on your view controller. Otherwise, skip this.
Delay api call. Since I'm using RxSwift, here's how I do it.
collectionView.rx.didEndDecelerating
.map { [unowned self] _ in self.refreshControl.isRefreshing }
.filter { $0 == true }
.subscribe(onNext: { _ in
// make api call
})
.disposed(by: disposeBag)
After API completes, call to
refreshControl.endRefreshing()
Caveat
Do note that since we delay API call, it means that this whole pull-to-refresh process is not as quick as it could have been done without the delay.
Unfortunately, no advice helped. But I found a solution that helped me. Setting the transparency of the navigation bar helped.enter image description here
Problem can be solved if add tableview or scroll view as root view in UIViewController hierarchy (like in UITableViewController)
override func loadView() {
view = customView
}
where customView is UITableView or UICollectionView

Make subview fit inside container and resize correctly

I'm trying to load dynamic nibs as subviews of containers. I almost got it to work, except that the subviews have an offset I can't seem to get rid off (cf pink view in pictures below).
From the View Hierarchy debugging:
As you can see in 2nd picture, the container frame is correctly positioned, whereas the subview isn't, for some reason.
I don't really know what is going with autolayout.
Here's the code that deals with loading the nib and assigning it as subview:
The commented-out code is all the things I've tried to make it work, with no success. I thought autolayout would work on its own without me having to do anything, but by default it loads the nib without resizing it.
That means the leading and top anchors are correct, however the nib then uses its full size... (cf picture below)
So the question is, what is needed for me to do in order to load the nib and make it fit to the container view ?
You should add constraints to your NibView instead of setting the bounds and the frame of the NibView.
Try to call the following function (addFullScreenConstraint) on the NibView after adding the NibView as a subview of the content view:
extension UIView {
/// Adds constraints to this `UIView` instances `superview` object
/// to make sure this always has the same size as the superview.
/// Please note that this has no effect if its `superview` is `nil`
/// – add this `UIView` instance as a subview before calling this.
func addFullScreenConstraints() {
guard let superview = self.superview else {
return
}
self.translatesAutoresizingMaskIntoConstraints = false
superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[subview]-0-|",
options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[subview]-0-|",
options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
}
}

UIImageView is not auto height in StackView with TableViewCell

This is the view hierarchy
I want the PhotoImgView auto height depends on the Image size
The image is download from server
I use KingFisher to download the image
when download finished, I set PhotoImgView's height constraint, and do layoutIfNeeded() in the cell class
but it's not update UI immediately
when I scroll to another cell, and let the cell invisible, and then scroll back, the cell would be right
How can I do this?
Same issue with the collectionView, I set collectionview's height constraint after collectionview.reloaData and get collectionview.contentSize.height
Try this:
Replace your current layoutIfNeeded() with this:
DispatchQueue.main.async {
self.layoutIfNeeded()
}
Try to call setNeedsLayout() before layoutIfNeeded().
To get right result for collection view height you can try:
collectionView.reloadData()
collectionView.setNeedsLayout()
collectionView.layoutIfNeeded()
collectionView.collectionViewLayout.invalidateLayout()

Swift autolayout: update view's dimension

I'm using PureLayout
I'm doing all the layout by code, by the way.
I have a UIScrollView that functions like Facebook's news feed. My scroll view has some subviews. Let's call 'em "cards".
I've set my constraints inside updateConstraints as such:
for (i, view) in enumerate(self.viewsForAutoLayout) {
/** Set view dimension **/
view.autoSetDimension(ALDimension.Width, toSize: view.frame.width)
view.autoSetDimension(ALDimension.Height, toSize: view.frame.height)
/** Make 'em stack **/
if i == 0 {
view.autoPinEdgeToSuperviewEdge(
ALEdge.Top,
withInset: offset
)
} else {
view.autoPinEdge(
ALEdge.Top,
toEdge: ALEdge.Bottom,
ofView: self.viewsForAutoLayout[i - 1],
withOffset: offset
)
}
}
self.setNeedsLayout()
And in layoutSubviews, this is where I resize the cards. Their respective height is being updated after an image is loaded from the internet (via NSURLConnection.sendAsynchronousRequest, fyi, but not related to this topic.) After that, I update my scrollView's height.
for (i, view) in enumerate(self.viewsForAutoLayout) {
view.realignContent()
}
totalHeight += view.frame.size.height + offset
let size = CGSizeMake(AppConstants.ScreenSize.SCREEN_WIDTH, totalHeight)
self.scrollView.resizeContent(size) // extension
The problem is that they're not being aligned properly. It's like calling setNeedsLayout() (which eventually calls layoutSubviews() -- correct me if I've mistaken) that does not align the content at all. I've also made sure that setNeedsUpdateConstraints() is called as well.
I've tried calling this in layoutSubviews...
view.autoSetDimension(ALDimension.Height, toSize: view.frame.height)
...but I get some kind of warning:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this: (1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand,
refer to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x1700918a0 V:[XXX.ContentBox:0x15fd63a80(928.986)]>",
"<NSLayoutConstraint:0x170098bf0 V:[XXX.ContentBox:0x15fd63a80(1275.99)]>"
)
(ContentBox is my UIView for the card)
The cards look messed up. What should I do to align my cards properly?
I'd gladly answer side comments regarding the code if it helps understand the question. :) Suggestions on the side aren't bad either.
Going by the warning you're seeing, it looks like you need to set view.translatesAutoresizingMaskIntoConstraints = false when you instantiate your card view.