Change size of view and move the surrounding views along - swift

I have a view that I want to change the size of with animation. Below that view are other views that I want to move accordingly.
I have created a new project just so simplify this for me to get this function to work properly. In this project I only have viewOne, viewTwo and a button to control this. There is also a boolean called "blue".
#IBAction func bttn() {
if blue {
blue = false
UIView.animate(withDuration: 1) {
self.viewOne.frame.size.height = 64
}
}else {
blue = true
self.viewOne.frame.size.height = 100
}
}
When I tap the button I expect viewOne to increase in size and viewTwo to be moved down since there is a constraint between the top and bottom of the views.
But the actual result is that viewOne increase but viewTwo does not move.

Only autolayout can do this as changing frames doesn't apply the constraints , You need to create a height outlet for view1 then
self.view1Height.constant = 64
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}

Related

Prevent cell content from "jumping" when applying constraint

I have a subclassed UICollectionViewCell and I want it to expand when tapped.
To achieve this, I put the title into a view ("titleStack") and the body into a separate view ("bodyStack"), and then put both of them into a container UIStackView ("mainStack"). I then constrain the contentView of the cell to the leading, trailing, and top edges of mainStack.
When the cell is selected, a constraint is applied that sets the bottom of the contentView's constraint to be the bottom of bodyStack. When it's unselected, I remove that constraint and instead apply one that sets the contentView's bottom constraint equal to titleStack's bottom constraint.
For the most part this works well, but when deselecting, there's this little jump, as you can see in this video:
What I would like is for titleStack to stay pinned to the top while the cell animates the shrinking portion, but it appears to jump to the bottom, giving it a sort of glitchy look. I'm wondering how I can change this.
I've pasted the relevant code below:
private func setUp() {
backgroundColor = .systemGray6
clipsToBounds = true
layer.cornerRadius = cornerRadius
setUpMainStack()
setUpConstraints()
updateAppearance()
}
private func setUpMainStack() {
contentView.constrain(mainStack, using: .edges, padding: 5, except: [.bottom])
mainStack.add([titleStack, bodyStack])
bodyStack.add([countryLabel, foundedLabel, codeLabel, nationalLabel])
}
private func setUpConstraints() {
titleStack.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
closedConstraint =
titleStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
closedConstraint?.priority = .defaultLow // use low priority so stack stays pinned to top of cell
openConstraint =
bodyStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
openConstraint?.priority = .defaultLow
}
/// Updates the views to reflect changes in selection
private func updateAppearance() {
UIView.animate(withDuration: 0.3) {
self.closedConstraint?.isActive = !self.isSelected
self.openConstraint?.isActive = self.isSelected
}
}
Thanks so much!
I was able to solve this by simply showing and hiding my "bodyStack" as well as using "layoutIfNeeded." I removed closedConstraint and openConstraint and just gave it a normal bottom constraint.
The relevant code:
func updateAppearance() {
UIView.animate(withDuration: 0.3) {
self.bodyStack.isHidden = !self.isSelected
self.layoutIfNeeded()
}
}

UIScrollView ContentOffset.y reset to 0 after performBatch

I'm using https://github.com/OfTheWolf/TwitterProfile <- this library on my project.
It is the little bit application of XLStripPager.
Because I have to set the header View and pager.
The layout architecture is
My Problem is this. When I set the expand the table cell label using UITableView.automaticDimension
expanding animation(?) is well worked. but after I scroll the table, the content offset.y is reset to zero. so even I tapped the bottom cell to expand, after that cell expanded when I move the scroll, the scrollView is set to the top(awkard...)
extension BottomViewController: LabelCellDelegate {
func expand(cell: LabelCell) {
let indexPath = cell.indexPath
if !expandCheck[indexPath!.row] {
expandCheck[indexPath!.row] = true
}
//offset = tableView.contentOffset.x
tableView.performBatchUpdates({ () -> Void in
cell.label.numberOfLines = 0
}, completion: nil)
}
}
I tried to print the scrollView Offset.
After click(expand) the cell the offset.y changed to 0...
I don't know how to solve this problem.
I try to assign the right before the expand contentOffset.y at the scrollViewDidScroll but breaking error.
How to prevent the reset to zero offset ? Any other method ?

Swift - Programmatically refresh constraints

My VC starts with stackView attached with Align Bottom to Safe Area .
I have tabBar, but in the beginning is hidden tabBar.isHidden = true.
Later when the tabBar appears, it hides the stackView
So I need function that refresh constraints after tabBar.isHidden = false
When I start the app with tabBar.isHidden = false the stackView is shown properly.
Tried with every function like: stackView.needsUpdateConstraints() , updateConstraints() , setNeedsUpdateConstraints() without success.
Now I'm changing the bottom programatically, but when I switch the tabBarIndex and return to that one with changed bottom constraints it detects the tabBar and lifts the stackView under another view (which is not attached with constraints). Like is refreshing again the constraints. I'm hiding and showing this stackView with constrains on/off screen.
I need to refresh constraints after tabBar.isHidden = false, but the constraints don't detect the appearance of the tabBar.
As I mention switching between tabBars fixes the issue, so some code executes to detecting tabBar after the switch. Is anyone know this code? I tried with calling the methods viewDidLayoutSubviews and viewWillLayoutSubviews without success... Any suggestions?
This amateur approach fixed my bug... :D
tabBarController!.selectedIndex = 1
tabBarController!.selectedIndex = 0
Or with an extension
extension UITabBarController {
// Basically just toggles the tabs to fix layout issues
func forceConstraintRefresh() {
// Get the indices we need
let prevIndex = selectedIndex
var newIndex = 0
// Find an unused index
let items = viewControllers ?? []
find: for i in 0..<items.count {
if (i != prevIndex) {
newIndex = i
break find
}
}
// Toggle the tabs
selectedIndex = newIndex
selectedIndex = prevIndex
}
}
Usage (called when switching dark / light mode):
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
tabBarController?.forceConstraintRefresh()
}
If you want to update view's layout, you can try layoutIfNeeded() function.
after updating stackView constraints call this method:
stackView.superview?.layoutIfNeeded()
Apple's Human Interface Guidelines indicate that one should not mess around with the Tab Bar, which is why (I'm guessing) setting tabBar.isHidden doesn't properly update the rest of the view hierarchy.
Quick searching comes up with various UITabBarController extensions for showing / hiding the tab bar... but they all appear to push the tabBar down off-screen, rather than setting its .isHidden property. May or may not be suitable for your use.
I'm assuming from your comments that your VC in tab index 0 has a button (or some other action) to show / hide the tabBar?
If so, here is an approach that may do the job....
Add this enum in your project:
enum TabBarState {
case toggle, show, hide
}
and put this func in that view controller:
func showOrHideTabBar(state: TabBarState? = .toggle) {
if let tbc = self.tabBarController {
let b: Bool = (state == .toggle) ? !tbc.tabBar.isHidden : state == .hide
guard b != tbc.tabBar.isHidden else {
return
}
tbc.tabBar.isHidden = b
view.frame.size.height -= 0.1
view.setNeedsLayout()
view.frame.size.height += 0.1
}
}
You can call it with:
// default: toggles isHidden
showOrHideTabBar()
// toggles isHidden
showOrHideTabBar(state: .toggle)
// SHOW tabBar (if it's hidden)
showOrHideTabBar(state: .show)
// HIDE tabBar (if it's showing)
showOrHideTabBar(state: .hide)
I would expect that simply pairing .setNeedsLayout() with .layoutIfNeeded() after setting the tabBar's .isHidden property should do the job, but apparently not.
The quick frame height change (combined with .setNeedsLayout()) does trigger auto-layout, though, and the height change is not visible.
NOTE: This is the result of very brief testing, on one device and one iOS version. I expect it will work across devices and versions, but I have not done complete testing.

Dragging NSSplitView divider does not resize views

I'm working with Cocoa and I create my views in code (no IB) and I'm hitting an issue with NSSplitView.
I have a NSSplitView that I configure in the following way in my view controller, in Swift:
override func viewDidLoad() {
super.viewDidLoad()
let splitView = NSSplitView()
splitView.isVertical = true
splitView.addArrangedSubview(self.createLeftPanel())
splitView.addArrangedSubview(self.createRightPanel())
splitView.adjustSubviews()
self.view.addSubview(splitView)
...
}
The resulting view shows the two subviews and the divider for the NSSplitView, and one view is wider than the other. When I drag the diver to change the width, as soon as I release the mouse, the divider goes back to its original position, as if pulled back by a "spring".
I can't resize the two subviews; the right one always keeps a fixed size. However, nowhere in the code I fix the width of that subview, or any of its content, to a constant.
What I would like to achieve instead is that the right view size is not fixed, and that if I drag the divider at halfway through, the subviews will resize accordingly and end up with the same width.
This is a screen recording of the problem:
Edit: here is how I set the constraints. I'm using Carthography, because otherwise setting constraints in code is extremely verbose beyond the most simple cases.
private func createLeftPanel() -> NSView {
let view = NSView()
let table = self.createTable()
view.addSubview(table)
constrain(view, table) { view, table in // Cartography magic.
table.edges == view.edges // this just constraints table.trailing to
// view.trailing, table.top to view.top, etc.
}
return view
}
private func createRightPanel() -> NSView {
let view = NSView()
let label = NSTextField(labelWithString: "Name of item")
view.addSubview(label)
constrain(view, label) { view, label in
label.edges == view.edges
}
return view
}

When I add a view using AutoLayout to a UIScrollView as a subview, everything looks wrong

My application contains a form that is presented to the user as a subview of a UIScrollView. The form view is controlled by a FormViewController. The UIScrollView takes up the whole screen. The form view has a height of 800 points and a width equal to the screen width. I want to place the form 70% towards the bottom of the screen, to let the user see an image below the scroll view. The form view contains labels, text fields, and other views all constrained using AutoLayout. The background of the form is white.
When I add the form view as a subview however, the labels show up above the main form area, in the area where the background is not white. This is what I'm using to add the form view to the UIScrollView in Swift:
func setUpForm() {
self.formContentHeight = 0
self.formContentHeight += CGFloat(Int(UIScreen.mainScreen().bounds.size.height * 0.7))
var form:FormViewController = self.storyboard?.instantiateViewControllerWithIdentifier("formviewcontroller") as! FormViewController
form.view.frame = CGRectMake(0, self.formContentHeight, self.formContentWidth, 800)
self.formScrollView.addSubview(form.view)
self.formContentHeight += 800
self.formScrollView.contentSize = CGSizeMake(self.formContentWidth, self.formContentHeight)
}
This is the result:
When I simply present the FormViewController using presentViewController(), everything is fine:
func setUpForm() {
presentViewController(form, animated: true, completion: nil)
}
Result:
Here's the FormViewController in the Storyboard:
What could be happening here?