I have a UITableView with a list of items that can be filtered. When the user chooses a filter, a UIView is exposed using a height NSLayoutConstraint that changes from 0 to 40 pixels:
The circle with the X is the clear button that clears the filter and then closes the UIView by changing the height constraint back to 0.
The problem is that when the UIView closes, that clear button doesn't completely go away:
Here's the relevant code:
class LiftLogViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
let coreDataStack = CoreDataStack()
var liftEvents = [LiftEvent]()
//MARK: IB outlets
#IBOutlet var tableView: UITableView!
#IBOutlet weak var navItem: UINavigationItem!
#IBOutlet weak var filterViewHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var clearFilterButton: UIImageView!
#IBOutlet weak var selectedFilter: UILabel!
#IBOutlet weak var clearButtonHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var clearButtonView: UIImageView!
var isFilterViewOpen = false
override func viewDidLoad() {
let doneButton = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(self.dismissLog(_:)))
let filterImage = UIImage(named: "filter_icon")
let filterButton = UIBarButtonItem(image: filterImage, style: .Plain, target: self, action: #selector(self.actionFilter))
self.navItem.rightBarButtonItems = [doneButton, filterButton]
let buttonTap = UITapGestureRecognizer(target: self, action: #selector(self.clearFilter))
clearFilterButton.addGestureRecognizer(buttonTap)
filterViewHeightConstraint.constant = 0.0
clearButtonHeightConstraint.constant = 0.0
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
let filterPredicate: NSPredicate?
if let logFilter = UserDefaultsManager.sharedInstance.logFilter?.rawValue {
filterPredicate = NSPredicate(format: "lift.liftName = [c] %#", logFilter)
print("viewWillAppear thinks the filter is \(logFilter)")
} else {
filterPredicate = nil
}
reloadData(filterPredicate)
let currentFilter = getCurrentLogFilter()
if currentFilter != nil {
selectedFilter.text = "Filtered by \(currentFilter!)"
isFilterViewOpen = true
clearButtonView.hidden = isFilterViewOpen ? false : true
} else {
selectedFilter.text = nil
}
super.viewWillAppear(animated)
}
override func viewDidAppear(animated: Bool) {
filterViewHeightConstraint.constant = isFilterViewOpen ? 40.0 : 0.0
clearButtonHeightConstraint.constant = isFilterViewOpen ? 21.0 : 0.0
clearButtonView.hidden = isFilterViewOpen ? false : true
UIView.animateWithDuration(0.33, delay: 0, options: [.CurveEaseOut], animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
func clearFilter() {
UserDefaultsManager.sharedInstance.logFilter = nil
isFilterViewOpen = !isFilterViewOpen
UIView.animateWithDuration(0.33, delay: 0, options: [.CurveEaseOut], animations: {
self.view.layoutIfNeeded()
}, completion: nil)
selectedFilter.text = nil
reloadData()
}
You can see I've tried both setting the .hidden value on the UIView that holds the clear button to true and I've tried changing the height constraint to 0.0 but neither of those make it gone completely.
Searching for a while hasn't produced any answers. Can anybody point out what's wrong with what I'm doing? Thanks.
It seems like the hair line color on the navigation bar may be clear resulting in the button still being slightly visible. Maybe change that and see if it covers the button. You can do so like this:
let view = UIView()
view.backgroundColor = UIColor.grayColor()
self.navigationController!.navigationBar.hairlineImageViewInNavigationBar(view)
As Dustin Spengler mentioned, the transparent hairline image view in the navigation bar causes the filter button to be slightly visible beneath it. As a workaround, you can hide the filter view (filterView.hidden = true) when constraint constant is set to 0.0 and unhide the filter view (filterView.hidden = false) when constraint constant is set to 40.0.
Thank you guys for your suggestions. It turns out that the solution was a little different, but your suggestions got me to poke around some more in that area and find the solution.
I neglected to mention that this was a view controller that I add a UIStackView that wrapped my UIView (the grey "filtered by" UIView) and the UITableView:
The problem was that the constraint I created from the top of the UIStackView to the top of its parent view was just slightly too big, pushing the top of the UIStackView just far enough to expose that little sliver. I reduced the Y distance to 63 and it's now perfect:
Thanks again for trying to help.
Related
Hi guys ı am stucked when ı try to present view controller . I split my main view to two views my desire is top view comes with .crossdisolve transition and below view comes with fade in transition . But these are in same view controller.Is this possible
.
This is how you try to do it in its simplest form.
You can adapt it with this or if you share a few code examples, I can be more helpful.
class ViewController: UIViewController {
#IBOutlet private weak var topView: UIView!
#IBOutlet private weak var bottomView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
topView.backgroundColor = .red
bottomView.alpha = 0.0
UIView.animate(withDuration: 1, delay: 0, options: .transitionCrossDissolve) { [weak self] in
self?.topView.backgroundColor = .blue
}
UIView.animate(withDuration: 1) { [weak self] in
self?.bottomView.alpha = 1.0
}
}
}
I have a custom view with stackview inside. In my stack view contains Image, Label1, and Label2 horizontally.
I set my custom view with constraints with height = 25 and width = 160.
I need to tap only on a specific part of the view. How can I do it?
For example, only Label2 can be clickable.
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let gesture = UITapGestureRecognizer(target: self, action: #selector(click))
label2.isUserInteractionEnabled = true
label2.addGestureRecognizer(gesture)
}
#objc func click(sender:UITapGestureRecognizer) {
}
You might want to use GestureRecognizers https://developer.apple.com/documentation/uikit/touches_presses_and_gestures
Is there a way to change view’s centerXAnchor constraint to another object (with animation) by pressing a button while the app is running?
Thank's for all of the answers!
First, you need to create an IBOutlet for your constraint from the storyboard, something similar to this.
#IBOutlet weak var centerXConstraint: NSLayoutConstraint!
And when the button is pressed, you should change the constraint value and update the view layout.
centerXConstraint.constant = setYourValueHere
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
If you want to add constraints programmatically, then remove #IBOutlet weak.
var centerXConstraint: NSLayoutConstraint!
Assign your view's center X anchor to it.
centerXConstraint = yourView.centerXAnchor.constraint(equalTo: super.view.centerXAnchor, constant: 0)
centerXConstraint.isActive = true
after that, you can change it as described above.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var centerXConstraintOutlet: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func buttonClickAction(_ sender: UIButton) {
animateViewCenterXConstraint()
}
func animateViewCenterXConstraint() {
UIView.animate(withDuration: 0.3) {
self.centerXConstraintOutlet.constant = 30
self.view.layoutIfNeeded()
}
}
}
1) Create view’s centerXAnchor constraints related to both the objects.
2) Take outlets of both the constraints.
3) Activate and de-activate them on click of the button as you wish.
Declare variable for centerXAnchor constraint and assign value on button action. You can try this:
var constraintOfCenterXAnchor: NSLayoutConstraint = NSLayoutConstraint()
in viewDidLoad() set constraintOfCenterXAnchor.constant = 0
When button pressed, change constraint value and update view
constraintOfCenterXAnchor.constant = 30
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
My View hierarchy looks like this:
ElevethViewController of type UIViewController
Container View
ManagedTableEleventhViewController of type UITableViewController embedded in Container View
ManagedTableEleventhViewController contains 4 static cells containing 1 textField each and one empty static cell.
class ManagedTableEleventhViewController: UITableViewController,UITextFieldDelegate {
var hasText:Bool!
#IBOutlet weak var fullName: UITextField!
#IBOutlet weak var flatNumber: UITextField!
#IBOutlet weak var streetAddress: UITextField!
#IBOutlet weak var phoneNumber: UITextField!
//checkValue takes ELViewController parameter so that segue can be
//performed when button is touched in EleventhViewController
func checkValue(ELViewController:EleventhViewController) {
//loop through the textfields and check if they have text
for case let textField as UITextField in viewController.view.subviews {
//print is not executed meaning loop is not performed
print("some text")
if textField.text == "" {
self.hasText = false
textField.layer.borderColor = UIColor.red.cgColor
} else {
print("true value in for loop")
self.hasText = true
performSegue(withIdentifier: "elevethToTwelveth", sender: ELViewController)
}
}//end of for loop
}
class EleventhViewController: UIViewController {
var nextButtonOutlet:UIButton!
override func viewDidLoad() {
super.viewDidLoad()
//create button programmatically
var button = UIButton(type: UIButtonType.custom) as UIButton
button = UIButton(frame: CGRect(x: 0, y: 637, width: 375, height: 50))
button.titleLabel?.textColor = UIColor.white
button.backgroundColor = UIColor(colorLiteralRed: 117/255, green: 232/255, blue: 0, alpha: 1)
button.setTitle("Next", for: .normal)
button.addTarget(self, action: #selector(EleventhViewController.nextButton), for: .touchUpInside)
self.view.addSubview(button)
self.nextButtonOutlet = button
}
func nextButton(sender: UIButton) {
//create instance of tableView
let managedTable = ManagedTableEleventhViewController()
managedTable.checkValue(viewController: self)
} //end of EleventhViewController class
Well first I can give you an answer that might satisfy you and fix your loop but I would recommend not doing it that way to alter your textfields. I would recommend doing it in cellForRow even though they may be static cells. Depending on your view setup in the cells it would look like this if the textfield is added directly to the cells and not to another view.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Testing")
for cell in tableView.visibleCells{
for sub in cell.contentView.subviews{
if sub is UITextField{
print("Textfield")
}
}
}
}
Just to follow up, if this is for validation you should'nt be only checking "" case, because you allow " ", " " etc. Use isEmpty, it should work better, if you only want to check existence of text.
Also you dont have to extract fields from subviews as you already have properties, i'm not sure if you have any other reason for this logic though.
Edit. Ooops, i just noticed your checking for textfields in a controller which does not have any visible fields, so normally your check never passes.
I think you should'nt even validate textfields for one class in another class, unless its a class handling textfield validation in general.
In EleventhViewController you have no textfields, so nothing to validate.
I have created a custom cell for my table view and it works fine except that my Image View will not align correctly.
Is there a way to add constraints to a custom cell view? Programmatically or otherwise?
This is what my cell looks like in the storyboard - which is what I was hoping to achieve at run time:
But when I demo the app this is what happens:
Here is another image from the storyboard:
View Controller /w TableView
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
println(questions)
println(finalResults)
let theWidth = view.frame.size.width
let theHeight = view.frame.size.height
tableView.frame = CGRectMake(0,0, theHeight, theWidth)
}
Cell View Controller
override func awakeFromNib() {
let theWidth = UIScreen.mainScreen().bounds.width
contentView.frame = CGRectMake(0,0, theWidth, 64)
answerImage.center = CGPointMake(115, 15)
}
You only have to set up your constraints correctly. Indeed, even if Add Missing Constraints and Reset to Suggested Constraints are handy sometimes they don't know what you want, thus the result cannot always be what you expect.
What you might want to do here is the following.
For you question label set it in center Y of it's container and set it's leading space to the superview. Like so :
Then for you image, you want to set it in center Y of it's container too and set a ratio 1:1 on it. Like so :
Note that you might also want to check the width and height if you don't want your image to upscale (if you set a bigger cell on some device).
You should now have something like that :
And the result :
Let me know if it helped.
PS : I don't have your image so I've just set a background color on the uiimageview
Assuming that you started with a View Controller, Dragged a table and a Cell into the table...
in ViewController: UIViewController...{
#IBOutlet weak var resultsTable: UITableView! // connect to tableView
override func viewDidLoad() {
super.viewDidLoad()
let theWidth = view.frame.size.width
let theHeight = view.frame.size.height
resultsTable.frame = CGRectMake(0,0, theWidth, theHeight)
}
}
in UITableViewCell file:
#IBOutlet weak var imageView: UIImageView!
override func awakeFromNib() {
let theWidth = UIScreen.mainScreen().bounds.width
contentView.frame = CGRectMake(0,0 theWidth, 64)
imageView.center = CGPointMake(115, 15) //mess around with these #
}