How to hide and unhide a view with height in swift? - swift

I am trying to hide and unhide a view. Its size should be 0 when it is hidden and about 200 when it is unhidden. I have two view controllers. When the first controller shows the view is hidden for the first time and its size is set to 0 and then it navigates to other controller and takes some values from the textfeilds and display them on a tableview in previous controller.
Now, I am able to hide the view for the first time with height 0 but when I take up the values the view is still hidden.
This is the code I have tried so far:
mainView.isHidden == true
mainView.heightAnchor.constraint(equalToConstant: CGFloat(0)).isActive = true
// when I get the values but this code doesn't work
mainView.isHidden == false
mainView.heightAnchor.constraint(equalToConstant: CGFloat(100)).isActive = true
Any help would be appreciated.

class viewController: UIViewController {
var height: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
height = mainView.heightAnch.constraint(equalToConstant: 0)
height.isActive = true
//handle change height
if mainView.isHidden == true {
height.constant = 0
}
else {
height.constant = 200
}
}
}

You have two options.
The first method set identifier to height constraint
set identifier:
then find and change with below code:
// first option
// find own constraint with indentifier
if let heightConstraint = self.myView.constraints.first(where: { item -> Bool in
return item.identifier == "heightIdentifier"
}) {
// set any constant to constraint
heightConstraint.constant = 200.0 // for hidden
heightConstraint.constant = 0.0 // for hide
// any work
}
second option: set IBOutlet to target constraint:
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
then change direct and simple:
// second option
// change constant direct
self.heightConstraint.constant = 200.0 // for hidden
self.heightConstraint.constant = 0.0 // for hide
// any work

You keep both created constraints in your view controller and activate however you need accordingly, using isActive property of NSLayoutConstraint:
var hiddenHeightConstraint: NSLayoutConstraint?
var showingHeightConstraint: NSLayoutConstraint?
var isMainViewHidden: Bool = false {
didSet {
mainView.isHidden == isMainViewHidden
hiddenHeightConstraint?.isActive = isMainViewHidden
showingHeightConstraint?.isActive = !isMainViewHidden
// Don't forget to call layoutIfNeeded() when you messing with the constraints
view.layoutIfNeeded()
}
}
override func viewDidLoad() {
super.viewDidLoad()
hiddenHeightConstraint = mainView.heightAnchor.constraint(equalToConstant: CGFloat(0))
showingHeightConstraint = mainView.heightAnchor.constraint(equalToConstant: CGFloat(100))
isMainViewHidden = false
}

Related

Updating variable in view constraint (SnapKit)

View is initialized with following constraints
View.snp.makeConstraints { (para) in
View.topConstraint = para.top.equalTo(parentview.snp.top).constraint
View.LeadingConstraint = para.leading.equalTo(parentview.snp.leading).constraint
View.TrailingConstraint = para.trailing.equalTo(parentview.snp.trailing).constraint
View.BottomConstraint =para.bottom.equalTo(parentview.snp.bottom).offset(-getheight).constraint
}
where getheight = parentview.frame.size.height/2 ;
when parentview changes its dimensions.View doesnt update its height as constraints are not called again.
any way to update or recall its constraints other the remakingConstraint which is not feasible at large scale.
Have tried:
View.updateConstraints()
View.setNeedsUpdateConstraints()
View.setNeedsLayout()
I need reference to each constraints because
if View.bottomTouch {
View.bottomConstraint.update(offset: View. BottomConstraint.layoutConstraints[0].constant + CurrentPoint - PreviousPoint)
}
Is there a reason you don't want to use 50% of the parent view height?
View.snp.makeConstraints { (para) in
para.top.equalTo(parentview.snp.top)
para.leading.equalTo(parentview.snp.leading)
para.trailing.equalTo(parentview.snp.trailing)
// 50% of the parent view height
para.height.equalTo(parentview.snp.height).multipliedBy(0.5)
// instead of this
//para.bottom.equalTo(parentview.snp.bottom).offset(-getheight)
}
Edit - after comments...
Keeping a reference to a constraint for the purposes of dragging a view is a very different question from "Keep the child view at 50% of the height of the parent view."
Give this a try...
It will create a cyan "parentView" with a blue "childView" (subview). Dragging the blue view (Pan Gesture) will drag its bottom up / down. Tapping anywhere (Tap Gesture) will toggle the insets on the frame of the parentView between 20 and 60.
When the parentView frame changes - either from the tap or, for example, on device rotation - the "childView" bottom will be reset to 50% of the height of the "parentView":
class ViewController: UIViewController {
let parentView = UIView()
let childView = UIView()
// childView bottom constraint
var bc: Constraint!
override func viewDidLoad() {
super.viewDidLoad()
parentView.backgroundColor = .cyan
childView.backgroundColor = .blue
parentView.addSubview(childView)
view.addSubview(parentView)
parentView.snp.makeConstraints { para in
para.top.leading.trailing.bottom.equalTo(self.view.safeAreaLayoutGuide).inset(20.0)
}
// childView's bottom constraint offset will be set in viewDidLayoutSubviews()
childView.snp.makeConstraints { para in
para.top.leading.trailing.equalToSuperview()
bc = para.bottom.equalToSuperview().constraint
}
let p = UIPanGestureRecognizer(target: self, action: #selector(panHandler(_:)))
childView.addGestureRecognizer(p)
let t = UITapGestureRecognizer(target: self, action: #selector(tapHandler(_:)))
view.addGestureRecognizer(t)
}
#objc func tapHandler(_ g: UITapGestureRecognizer) -> Void {
// on tap, toggle parentView inset
// between 20 and 60
// this will trigger viewDidLayoutSubviews(), where the childView bottom
// constraint will be reset to 50% of the parentView height
var i: CGFloat = 60.0
if parentView.frame.origin.x > 20 {
i = 20.0
}
parentView.snp.updateConstraints { para in
para.top.leading.trailing.bottom.equalTo(self.view.safeAreaLayoutGuide).inset(i)
}
}
#objc func panHandler(_ g: UIPanGestureRecognizer) -> Void {
let translation = g.translation(in: g.view)
// update bottom constraint constant
bc.layoutConstraints[0].constant += translation.y
// reset gesture translation
g.setTranslation(CGPoint.zero, in: self.view)
}
var parentViewHeight: CGFloat = 0.0
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// reset childView's bottom constraint
// to 50% of its superView's height
// ONLY if parentView frame height has changed
if parentView.frame.height != parentViewHeight {
parentViewHeight = parentView.frame.height
bc.layoutConstraints[0].constant = -parentViewHeight * 0.5
}
}
}
Firstly, check whether the getheight value did update when the parent view layout change. In order to reload the existing constraints, you may need to call layoutIfNeeded() of your parent view.

How to make scrollview scroll separately to its content height in swift

I have view hierarchy like below in storyboard
here for content main constrains top = 0, leading = 0, trailing = 0, bottom = 0
here for scrollview constrains top = 0, leading = 0, trailing = 0, bottom = 0
here for View constrains top = 0, leading = 0, trailing = 0, bottom = 0
for ContentView constrains top = 0, leading = 0, trailing = 0
for TblReview constrains top = 0, leading = 0, trailing = 0, bottom = 20
for Productcollectionview constrains top = 0, leading = 0, trailing = 0, bottom = 20, height = 400
and i have Productcollectionview height outlet like below in swift file
and i don't want collectionview separate scrolling.. i want total view to scroll according to collectionview cells.. so for that i have written below code but with this code contentView and tblReview also scrolling upto productioncollectionview height i need contentView should scroll upto its content height and tblReview should scroll upto its rows
how to make scrolling separately to its height.
please help me to solve this issue
#IBOutlet weak var productCollHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
productCollectionView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
let collectionView = object as? UICollectionView
if collectionView == self.productCollectionView{
if(keyPath == "contentSize"){
if let newvalue = change?[.newKey]
{
let newsize = newvalue as! CGSize
self.productCollHeight.constant = newsize.height
}
}
}
}
#IBAction func aboutCompany(_ sender: UIButton){
self.productCollectionView.isHidden = true
self.tblReview.isHidden = true
self.contentView.isHidden = false
}
#IBAction func review(_ sender: UIButton){
self.productCollectionView.isHidden = true
self.tblReview.isHidden = false
self.contentView.isHidden = true
}
#IBAction func productOfSeller(_ sender: UIButton){
self.productCollectionView.isHidden = false
self.tblReview.isHidden = true
self.contentView.isHidden = true
}
and i am hiding and showing tblReview and contentView according to need
with the above code tblReview and contentView are also scrolling upto productCollectionView height.. please help me to solve this error
EDIT: share this seller is out of scrollview so here contentView height is not not so long but if i scroll the contentView also scrolling too long like productioncollectionview
this is contentView which is scrolling too long like productioncollectionview
if your all constraint are proper than just use it like this you don't need to count every time. UICollectionView has intrinsicContentSize it will count it properly.
final class ContentSizedCollectionView: UICollectionView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
put that code in controller or wherever you wan to put and assign ContentSizedCollectionView class to your collectionview.
NOTE: you can also use it with UITableView after creating for UITableView.

Swift: button added within UIView not clickable

I have the following container view:
class NotificationsContainer: UIView {
init() {
super.init(frame: .zero)
controller.view.translatesAutoresizingMaskIntoConstraints = false
addSubview(controller.view)
controller.view.isHidden = true
self.isUserInteractionEnabled = true
self.clipsToBounds = false
configureAutoLayout()
}
var showNotifications = false {
didSet {
if showNotifications == true {
controller.view.isHidden = false
} else {
controller.view.isHidden = true
}
}
}
internal lazy var notificationBanner: AlertView = {
let banner = AlertView()
banner.attrString = UploadNotificationManager.shared.notificationBannerText()
banner.alertType = .notification
banner.translatesAutoresizingMaskIntoConstraints = false
addSubview(banner)
banner.isUserInteractionEnabled = true
banner.showMeButton.addTarget(self, action: #selector(showHideNotifications), for: .touchDown)
return banner
}()
#objc func showHideNotifications() {
showNotifications = showNotifications == false ? true : false
}
private lazy var notificationView: NotificationContentView = {
let notificationView = NotificationContentView()
return notificationView
}()
private lazy var controller: UIHostingController = {
return UIHostingController(rootView: notificationView)
}()
private func configureAutoLayout() {
NSLayoutConstraint.activate([
notificationBanner.leadingAnchor.constraint(equalTo: leadingAnchor),
notificationBanner.trailingAnchor.constraint(equalTo: trailingAnchor),
controller.view.trailingAnchor.constraint(equalTo: notificationBanner.trailingAnchor),
controller.view.topAnchor.constraint(equalTo: notificationBanner.bottomAnchor)
])
}
}
AlertView contains a button as follows:
internal lazy var showMeButton: UIButton = {
let button = UIButton()
button.setTitle("Show me...", for: .normal)
button.setTitleColor(UIColor.i6.blue, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: Constants.fontSize)
addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
Then I add the container view to my main view:
private lazy var notifications: NotificationsContainer = {
let notifications = NotificationsContainer()
notifications.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(notifications)
notifications.leadingAnchor.constraint(equalTo: flightNumber.leadingAnchor).isActive = true
notifications.trailingAnchor.constraint(equalTo: flightNumber.trailingAnchor).isActive = true
return notifications
}()
override public func viewDidLoad() {
super.viewDidLoad()
stackView.insert(arrangedSubview: notifications, atIndex: 0)
}
Now as you can see I am trying to add an action to the showMeButton. However, when I click on the button, it does nothing. I have read before that this could be to do with the frame of the container view. However, I have tried setting the height of the notification view in my main view (width should already be there due to leading and trailing constraints) and I have tried setting the height of notificationBanner as well but nothing is working.
Here is the view in the view debugger:
The showMe button does not appear to be obscured and all other views appear to have dimensions...
Look at the debug view hierarchy in Xcode and see if the view containing the button is actually showing up. You haven't set enough constraints on any of these views so the height and width look like they could be ambiguous to me. Once you're inside the view debugger, another common problem is that another invisible view is covering up the one with the button and intercepting the touch gestures.

How to add a view in a NSStackView with animation?

In interface builder, I have several views (A, B, C) in a NSStackView (vertical orientation).
During runtime, I change dynamically the NSStackView by showing or hiding (isHidden) some of these embedded views through a property observer (willSet). If the code below actually works (the views show or hide accordingly), I can't manage to do it with animation.
var isExpanded :Bool = false {
willSet {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = 2.0
if newValue {
viewA.isHidden = true
viewB.isHidden = false
viewC.isHidden = true
viewD.isHidden = true
print("Popover expanded")
} else {
viewA.isHidden = false
viewB.isHidden = false
viewC.isHidden = false
viewD.isHidden = false
print("Popover contracted")
}
NSAnimationContext.endGrouping()
}
As I understand, the isHidden state is not handled by the animation but I don't find other ways to do it.
Alternatively, I also tried to use addView and removeFromSuperview method (instead of hiding/showing). Same results...
My problem is that I mainly find iOS-related problems (UIView.animate...), and none about MacOS (NSView)...
Any ideasĀ ?
Many thanks for your help, Jo
I had the wrong approach: isHidden is not the right approach (can't animate a discrete value - it's hidden or not).
Instead, I added a constraint on the view's height
Connect the constraint in the viewController as an IBOutlet. With this code, the view smoothly squeeze in between 2 other views in a stackView. :-)
#IBOutlet weak var constraint: NSLayoutConstraint!
#IBAction func toggle(_ sender: NSButton) {
if constraint.constant == 0 {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
constraint.constant = 80
self.view.layoutSubtreeIfNeeded()
}, completionHandler: nil)
} else {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
constraint.constant = 0
self.view.layoutSubtreeIfNeeded()
}, completionHandler: nil)
}
}
Hope it helps.
Jo

Check text field Live

I have found this answer How to check text field input at real time?
This is what I am looking for. However I am having trouble actually implementing this code. Also my current geographical location makes googling almost impossible.
I want to be able to change the background color of the next text field if the correct number is entered into the previous text field. textfieldTwo background color will change to green if the correct value is entered in textFieldOne. If the value is incorrect then nothing will happen. Please help me out. I have two text fields called textFieldOne and textFieldTwo and nothing else in the code.
Just pop this in your main view controller in an empty project (try using iphone 6 on the simulator)
import UIKit
class ViewController: UIViewController {
var txtField:UITextField!
var txtFieldTwo:UITextField!
var rightNumber = 10
override func viewDidLoad() {
super.viewDidLoad()
//txtFieldOne
var txtField = UITextField()
txtField.frame = CGRectMake(100, 100, 200, 40)
txtField.borderStyle = UITextBorderStyle.None
txtField.backgroundColor = UIColor.blueColor()
txtField.layer.cornerRadius = 5
self.view.addSubview(txtField)
//txtFieldTwo
var txtFieldTwo = UITextField()
txtFieldTwo.frame = CGRectMake(100, 150, 200, 40)
txtFieldTwo.borderStyle = UITextBorderStyle.None
txtFieldTwo.backgroundColor = UIColor.blueColor()
txtFieldTwo.layer.cornerRadius = 5
self.view.addSubview(txtFieldTwo)
txtField.addTarget(self, action: "checkForRightNumber", forControlEvents: UIControlEvents.AllEditingEvents)
self.txtField = txtField
self.txtFieldTwo = txtFieldTwo
}
func checkForRightNumber() {
let number:Int? = self.txtField.text.toInt()
if number == rightNumber {
self.txtFieldTwo.backgroundColor = UIColor.greenColor()
} else {
self.txtFieldTwo.backgroundColor = UIColor.blueColor()
}
}
}
EDIT: Adding a version with IBOutlets and IBActions
Note that in this example the IBAction is connected to txtFieldOne on Sent Events / Editing Changed
Also, make sure your Text Fields border colors are set to None. In the storyboard, the way to do this is to choose the left most option with the dashed border around it. That's so you can color the backgrounds. You can use layer.cornerRadius to set the roundness of the border's edges.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var txtField: UITextField!
#IBOutlet weak var txtFieldTwo: UITextField!
var rightNumber = 10
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func checkForRightNumber(sender: AnyObject) {
let number:Int? = self.txtField.text.toInt()
if number == rightNumber {
self.txtFieldTwo.backgroundColor = UIColor.greenColor()
} else {
self.txtFieldTwo.backgroundColor = UIColor.blueColor()
}
}
}