Protocol delegate not giving desired result - ios

A simple profile page with an image to display based on what i select on the settings page, different files for controller and view, click on tick of profile go to settings page, select image1 or image2 and that image must display on profile page ,i try and create a protocol on settings to be able to add image , then implement a delegate on profile view file so that it can update the image its not working, can any one please point out my error
SettingsController
import UIKit
protocol ShowImage: class {
func displayImage(_ of: UIImage)
}
class SettingsController: UIViewController {
weak var delegate: ShowImage?
override func viewDidLoad() {
super.viewDidLoad()
let settings = SettingsView()
view.addSubview(settings.view)
settings.btn1.addTarget(self, action: #selector(dCode), for: .touchUpInside)
// Do any additional setup after loading the view.
}
#objc func dCode() {
let image = UIImage(named: "homei")
delegate?.displayImage(image!)
navigationController?.pushViewController(ProfileController(), animated: true)
}
}
SettingsView
import UIKit
class SettingsView: UIViewController {
var btn1 = UIButton()
var btn2 = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
view.backgroundColor = UIColor.white
btn1.heightAnchor.constraint(equalToConstant: 30).isActive = true
btn1.widthAnchor.constraint(equalToConstant: 150).isActive = true
btn1.setTitleColor(UIColor.red, for: .normal)
btn1.backgroundColor = UIColor.green
btn2.heightAnchor.constraint(equalToConstant: 30).isActive = true
btn2.widthAnchor.constraint(equalToConstant: 150).isActive = true
btn2.setTitleColor(UIColor.red, for: .normal)
btn2.backgroundColor = UIColor.green
btn1.setTitle("Image1", for: .normal)
btn2.setTitle("Image2", for: .normal)
let stackP = UIStackView()
stackP.axis = .horizontal
stackP.alignment = .top
stackP.spacing = 10
stackP.distribution = .fill
stackP.addArrangedSubview(btn1)
stackP.addArrangedSubview(btn2)
stackP.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackP)
stackP.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackP.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// Do any additional setup after loading the view.
}
}
Profile Controller
import UIKit
class ProfileController: UIViewController {
let profile = ProfileView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(profile.view)
// Do any additional setup after loading the view.
profile.settingsBtn.addTarget(self, action: #selector(gotoSettings), for: .touchUpInside)
}
#objc func gotoSettings(){
let settings = SettingsController()
navigationController?.pushViewController(settings, animated: true)
}
}
Profile View
import UIKit
class ProfileView: UIViewController, ShowImage{
func displayImage(_ of: UIImage) {
apply(img: of)
}
var bgImage = UIImageView()
var settingsBtn = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
bgImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bgImage)
bgImage.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
bgImage.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
settingsBtn.heightAnchor.constraint(equalToConstant: 60).isActive = true
settingsBtn.widthAnchor.constraint(equalToConstant: 60).isActive = true
// settingsBtn.setTitle("Settings", for: .normal)
settingsBtn.setImage(UIImage(named: "tick"), for: .normal)
settingsBtn.backgroundColor = UIColor.red
settingsBtn.layer.cornerRadius = 5
view.addSubview(settingsBtn)
settingsBtn.translatesAutoresizingMaskIntoConstraints = false
settingsBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
settingsBtn.topAnchor.constraint(equalTo: view.topAnchor, constant: 70).isActive = true
let set = SettingsController()
set.delegate = self
// Do any additional setup after loading the view.
}
func apply(img: UIImage)
{
bgImage.image = img
}
}
enter image description here

let set = SettingsController()
set.delegate = self
You're making a brand new SettingsController, setting its delegate... and then doing nothing with it. You probably want to find the existing SettingsController that is on the screen, though you have a very confusing system here with xView and xController, both of which are view controllers, so it's hard to know what's going on.

Related

iOS Swift protocol & delegate

Why my delegation is not working?
With my sample the action button works when clicked, but for some reason it does not reach the didAction function in my second controller.
protocol HomeControllerDelegate: class {
func didAction()
}
class HomeController: UIViewController {
weak var delegate: HomeControllerDelegate?
private let actionButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Action", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .black
button.setHeight(50)
button.setWidth(100)
button.addTarget(self, action: #selector(handleAction), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(actionButton)
actionButton.centerX(inView: view)
actionButton.centerY(inView: view)
}
#objc func handleAction() {
print("DEBUG: Handle Action Button delegate here....")
delegate?.didAction()
}
}
class SecondController: UIViewController {
let homeController = HomeController()
override func viewDidLoad() {
super.viewDidLoad()
homeController.delegate = self
}
}
extension SecondController: HomeControllerDelegate {
func didAction() {
print("DEBUG: In SecondController - didAction()")
}
}
Many thanks for the guidance, it has made me look at the fundamentals and also what to research.
I have created the below which solves my problem.
A container controller that Instantiates two ViewControlers and sets them as childVC's
Then created a protocol on FirstChildVC and SecondChildVC is the delegate
All is working now an I understand a lot better.
class ContainerController: UIViewController {
let secondChildVC = SecondChildVC()
let firstChildVC = FirstChildVC()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
addFirstChildVC()
addSecondChildVC()
}
func addSecondChildVC(){
addChild(secondChildVC)
view.addSubview(secondChildVC.view)
secondChildVC.didMove(toParent: self)
setSecondChildVCConstraints()
}
func addFirstChildVC(){
addChild(firstChildVC)
view.addSubview(firstChildVC.view)
firstChildVC.delegateActionButton = secondChildVC
firstChildVC.didMove(toParent: self)
setFirstChildVCConstraints()
}
func setFirstChildVCConstraints() {
firstChildVC.view.translatesAutoresizingMaskIntoConstraints = false
firstChildVC.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
firstChildVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
firstChildVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
firstChildVC.view.heightAnchor.constraint(equalToConstant: 200).isActive = true
}
func setSecondChildVCConstraints() {
secondChildVC.view.translatesAutoresizingMaskIntoConstraints = false
secondChildVC.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
secondChildVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
secondChildVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
secondChildVC.view.heightAnchor.constraint(equalToConstant: 200).isActive = true
}
}
protocol FirstChildVCDelegate: class {
func didAction(data: String)
}
class FirstChildVC: UIViewController {
weak var delegateActionButton: FirstChildVCDelegate!
private let actionButton: UIButton = {
let button = UIButton(type: .system)
let buttonHeight = 30
button.setTitle("Button", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .black
button.layer.cornerRadius = CGFloat(buttonHeight / 2)
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: CGFloat(buttonHeight)).isActive = true
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.addTarget(self, action: #selector(handleAction), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
configureActionButtonView()
}
func configureActionButtonView() {
view.addSubview(actionButton)
actionButton.translatesAutoresizingMaskIntoConstraints = false
actionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
actionButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc func handleAction() {
delegateActionButton.didAction(data: "Button Pressed")
}
}
class SecondChildVC: UIViewController {
private let actionButtonLabel: UILabel = {
let label = UILabel()
label.text = "test"
label.font = .systemFont(ofSize: 18)
label.textColor = .white
label.isHidden = true
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPurple
configureActionButtonLabel()
}
func configureActionButtonLabel() {
view.addSubview(actionButtonLabel)
actionButtonLabel.translatesAutoresizingMaskIntoConstraints = false
actionButtonLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
actionButtonLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
extension SecondChildVC: FirstChildVCDelegate {
func didAction(data: String) {
actionButtonLabel.isHidden.toggle()
actionButtonLabel.text = data
}
}
Initial State
State once Button pressed

Protocol and delegate pattern not calling the method

Implementing a protocol/delegate pattern as suggested by expert #DonMag, i am unable to get the code working...
I show a ratings window like below and when the user changes the ratings , i want the ratings emoji to update and the controller to pop, i also use animation, now for view i have a separate ratings class and for controller a separate class,
The problem is this function gets tapped , i can detect it but it does not reach the protocol method
#objc func ratingButtonTapped(_ sender: UIButton) {
// print(sender.titleLabel?.text)
guard let t = sender.titleLabel?.text else {
return
}
ratingDelegate?.defineTheratings(t)
}
Controller class
import UIKit
import CoreData
protocol RatingsPresentation: class {
func defineTheratings(_ ratings: String)
}
class RatingsViewController: UIViewController, RatingsPresentation {
func defineTheratings(_ ratings: String) {
print("ratings seleced\(ratings)")
self.restaurant.rating = ratings
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
appDelegate.saveContext()
}
if self.presentedViewController != nil {
self.dismiss(animated: true, completion: nil)
}
else {
self.navigationController?.popViewController(animated: true)
}
}
var restaurant: Restaurant!
var rates = RatingsView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rates.view)
//Delegates
let vc = RatingsView()
// set the rating delegate
vc.ratingDelegate = self
//
if let restaurantImage = restaurant.image {
rates.bkgImageView.image = UIImage(data: restaurantImage as Data)
}
rates.crossBtn.addTarget(self, action: #selector(closeRatings), for: .touchUpInside)
let animateCross = CGAffineTransform(translationX: 1000, y: 0)
rates.crossBtn.transform = animateCross
}
#objc func closeRatings() {
navigationController?.popViewController(animated: true)
}
}
View Class
import UIKit
class RatingsView: UIViewController {
var bkgImageView = UIImageView()
var crossBtn = UIButton()
var btnCollection: [UIButton] = []
weak var ratingDelegate: RatingsPresentation?
override func viewDidLoad() {
super.viewDidLoad()
let ratings: [String] = ["cool", "happy","love", "sad", "angry"]
let animationBlur = UIBlurEffect(style: .dark)
let visualBlurView = UIVisualEffectView(effect: animationBlur)
// add elements to self
view.addSubview(bkgImageView)
view.addSubview(visualBlurView)
bkgImageView.translatesAutoresizingMaskIntoConstraints = false
visualBlurView.translatesAutoresizingMaskIntoConstraints = false
bkgImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
bkgImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
bkgImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
bkgImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// constrain visualBlurView to all 4 sides
visualBlurView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
visualBlurView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
visualBlurView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
visualBlurView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
bkgImageView.contentMode = .scaleAspectFill
// Do any additional setup after loading the view.
//Add stackView
let stackEmoji = UIStackView()
stackEmoji.translatesAutoresizingMaskIntoConstraints = false
stackEmoji.axis = .vertical
stackEmoji.spacing = 5
stackEmoji.distribution = .fill
stackEmoji.alignment = .top
let font = UIFont(name: "Rubik-Medium", size: 28)
let fontM = UIFontMetrics(forTextStyle: .body)
ratings.forEach { (btn) in
let b = UIButton()
b.setTitle(btn, for: .normal)
b.titleLabel?.font = fontM.scaledFont(for: font!)
b.setImage(UIImage(named: btn), for: .normal)
stackEmoji.addArrangedSubview(b)
btnCollection.append(b)
//btn animation
let sizeAnimation = CGAffineTransform(scaleX: 5, y: 5)
let positionAnimation = CGAffineTransform(translationX: 1000, y: 0)
let combinedAninaton = sizeAnimation.concatenating(positionAnimation)
b.transform = combinedAninaton
b.addTarget(self, action: #selector(ratingButtonTapped(_:)), for: .touchUpInside)
}
view.addSubview(stackEmoji)
stackEmoji.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackEmoji.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// if let img = UIImage(named: "cross") {
// crossBtn.setImage(img, for: [])
// }
crossBtn.setTitle("X", for: [])
crossBtn.setTitleColor(.white, for: .normal)
crossBtn.setTitleColor(.gray, for: .highlighted)
crossBtn.titleLabel?.font = UIFont.systemFont(ofSize: 44, weight: .bold)
view.addSubview(crossBtn)
crossBtn.translatesAutoresizingMaskIntoConstraints = false
crossBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
crossBtn.topAnchor.constraint(equalTo: view.topAnchor, constant: 150).isActive = true
}
#objc func ratingButtonTapped(_ sender: UIButton) {
// print(sender.titleLabel?.text)
guard let t = sender.titleLabel?.text else {
return
}
ratingDelegate?.defineTheratings(t)
}
}
Problem is here
var rates = RatingsView() //// shown 1
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rates.view) /////// you add this as a subview
//Delegates
let vc = RatingsView() //////// this is useless - not shown 1
// set the rating delegate
vc.ratingDelegate = self ////// and set the delegate for this non-shown
So you need to remove let vc = RatingsView() and set the delegate for
rates.ratingDelegate = self

What error am I making when trying to pass data between ViewControllers using closures?

My main View Controller has an embedded UITabbarController in it. There are 2 subVCs: viewControllerONE & viewControllerTWO.
viewControllerONE displays a label showing the user's current score, and viewControllerTWO displays a button, pressing which should display an error window on viewControllerONE.
(For the sake of simplicity, the user has to manually navigate to viewControllerONE using the tab bar to see the error, ie, pressing the button on viewControllerTWO doesn't take you to viewControllerONE and then display the errorWindow.)
The error window is a simple UIView class.
From SO, I have learnt that the best way to pass data in swift isn't delegates but closures, as the former is more of an objective C design, and so I've used closures to pass data between view controllers.
So here is my viewControllerONE code:
class ViewControllerONE: UIViewController {
var score = 10
lazy var scoreLabel: UILabel = {
let label = UILabel()
label.text = String(score)
label.font = .systemFont(ofSize: 80)
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scoreLabel)
let VC = ViewControllerTWO()
VC.callback = { [weak self ] in
let error = errorView()
self?.view.addSubview(error)
}
NSLayoutConstraint.activate([
scoreLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scoreLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
}
and here is my viewControllerTWO code:
class ViewControllerTWO: UIViewController {
var callback: (() -> Void)?
let buttonTwo: UIButton = {
let button = UIButton()
button.setTitle("HIT THIS BUTTON!", for: .normal)
button.backgroundColor = .systemBlue
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(buttonTwoPressed), for: .touchUpInside)
button.layer.cornerRadius = 8
return button
}()
#objc func buttonTwoPressed() {
print("PRESSEDDDD")
callback?()
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(buttonTwo)
NSLayoutConstraint.activate([
buttonTwo.centerXAnchor.constraint(equalTo: view.centerXAnchor),
buttonTwo.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
And here is the error view:
class ErrorView: UIView {
fileprivate let dismissButton: UIButton = {
let button = UIButton()
button.setTitle("DISMISS!!!!", for: .normal)
button.backgroundColor = .systemBlue
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(dismissButtonPressed), for: .touchUpInside)
button.layer.cornerRadius = 12
return button
}()
#objc func dismissButtonPressed() {
self.errorGoAway()
}
fileprivate let errorViewBox: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
v.layer.cornerRadius = 24
return v
}()
#objc fileprivate func errorGoAway() {
self.alpha = 0
}
#objc fileprivate func errorShow() {
self.alpha = 1
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(errorGoAway)))
self.backgroundColor = UIColor.gray
self.backgroundColor?.withAlphaComponent(0.8)
self.frame = UIScreen.main.bounds
self.addSubview(errorViewBox)
errorViewBox.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
errorViewBox.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
errorViewBox.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7).isActive = true
errorViewBox.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.45).isActive = true
errorViewBox.addSubview(dismissButton)
dismissButton.leadingAnchor.constraint(equalTo: errorViewBox.leadingAnchor).isActive = true
dismissButton.trailingAnchor.constraint(equalTo: errorViewBox.trailingAnchor).isActive = true
dismissButton.centerYAnchor.constraint(equalTo: errorViewBox.centerYAnchor).isActive = true
dismissButton.heightAnchor.constraint(equalTo: errorViewBox.heightAnchor, multiplier: 0.15).isActive = true
errorShow()
}
You need to get your controller from tabBarController instead of create new instance:
class ViewControllerONE: UIViewController {
var score = 10
lazy var scoreLabel: UILabel = {
let label = UILabel()
label.text = String(score)
label.font = .systemFont(ofSize: 80)
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = .blue
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scoreLabel)
var VC: ViewControllerTWO?
let arrayOfVC = self.tabBarController?.viewControllers ?? []
for item in arrayOfVC {
if let secondVC = item as? ViewControllerTWO {
VC = secondVC
break
}
}
VC?.callback = { [weak self ] in
let error = ErrorView()
self?.view.addSubview(error)
}
NSLayoutConstraint.activate([
scoreLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scoreLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
}

Interacting with contents of UIPopoverPresentationController on regular sized iPhones?

It appears - at least by default, that while you can interact with the contents of a popover on a plus iPhone and dismiss it by tapping on the background, on a regular, non plus phone, the behavior is the opposite.
Does anyone know how to correct and/or configure this so that you can interact with a popover on an regular iPhone?
I have a sample that demonstrates the problem here:
https://github.com/chrisco314/iPhone-Popover-Test,
but the relevant code is:
class PresentingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
func configure() {
view.addSubview(button)
view.backgroundColor = .white
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 50).isActive = true
}
lazy var button: UIButton = {
let button = UIButton()
button.layer.cornerRadius = 10
button.contentEdgeInsets = .init(top: 8, left: 8, bottom: 8, right: 8)
button.backgroundColor = .blue
button.setTitle("Show popover", for: .normal)
button.addTarget(self, action: #selector(didTap(sender:)), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#objc func didTap(sender: UIButton) {
let presented = PresentedViewController()
presented.modalPresentationStyle = .popover
let popover = presented.popoverPresentationController!
popover.delegate = self
popover.sourceRect = sender.bounds
popover.sourceView = sender
popover.permittedArrowDirections = .up
popover.backgroundColor = popover.presentedViewController.view.backgroundColor
self.present(presented, animated: true, completion: {})
}
}
extension PresentingViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
class PresentedViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
func configure() {
view.backgroundColor = .green
view.addSubview(text)
text.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
view.rightAnchor.constraint(equalTo: text.rightAnchor, constant: 20).isActive = true
text.topAnchor.constraint(equalTo: view.topAnchor, constant: 50).isActive = true
view.bottomAnchor.constraint(greaterThanOrEqualTo: text.bottomAnchor, constant: 50).isActive = true
}
lazy var text: UITextField = {
let view = UITextField()
view.text = "Placeholder"
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .blue
return view
}()
}
Thanks!
I am thinking now that this might have been a simulator problem. I had seen this on my main project, a separate sample, and then wrote a new cleaner sample for public consumption. I then switched back and forth between different versions of the simulator and it started working again.
Weird.

Constraints not changing when user cancels searching

So I have a search icon as my right bar button item. When the user taps the icon, it allows the user to search and only show certain values in the tableview. It also hides the nav bar buttons at the top and the filterBar just below the navigation controller
func setupNavBarButtons() {
let searchImage = UIImage(named: "search_icon")?.withRenderingMode(.alwaysOriginal)
let searchBarButtonItem = UIBarButtonItem(image: searchImage, style: .plain, target: self, action: #selector(handleSearch))
navigationItem.rightBarButtonItem = searchBarButtonItem
setupFilterButton()
}
filter bar and navbar items to be hidden while searching like so:
func handleSearch() {
searchController.searchBar.isHidden = false
navigationItem.titleView = searchController.searchBar
searchController.searchBar.becomeFirstResponder()
navigationItem.rightBarButtonItems = nil
navigationItem.leftBarButtonItems = nil
filterBar.isHidden = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
And then it to reappear again once user stops searching, like so:
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
setupNavBarButtons()
searchController.searchBar.isHidden = true
filterBar.isHidden = false
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
// Also tried tableView.topAnchor.constraint(equalTo: filterBar.bottomAnchor).isActive = true
}
Before Search:
During Search
After search: As you can see the tableview doesn't return to where it originally was. The filterBar is the gray view with 'Map' and 'Location'
Still got the same issue so I've uploaded my project here:
https://github.com/lukejones1/bug
At first, You added 'height' layout constraint with 40 px.
and you added 'height' layout constraint with 0 px when user clicked search button.
and again, you added 'height' layout constraint with 40 px when user clicked cancel button.
You need to reuse the layout constraint.
class ViewController: UIViewController {
var filterBarHeightLC : NSLayoutConstraint?
lazy var tableView : UITableView = {
let tv = UITableView()
tv.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
tv.layoutMargins = UIEdgeInsets.zero
tv.separatorInset = UIEdgeInsets.zero
tv.backgroundColor = .red
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
lazy var filterBar : UIView = {
let bar = UIView()
bar.backgroundColor = .blue
bar.translatesAutoresizingMaskIntoConstraints = false
return bar
}()
fileprivate lazy var filterButton : UIButton = {
let button = UIButton()
button.setTitleColor(UIColor.white, for: UIControlState())
button.setTitle("Filter", for: UIControlState())
button.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
fileprivate lazy var searchController: UISearchController = {
let sc = UISearchController(searchResultsController: nil)
sc.dimsBackgroundDuringPresentation = false
sc.hidesNavigationBarDuringPresentation = false
sc.searchResultsUpdater = self
sc.delegate = self
sc.view.tintColor = UIColor.white
sc.searchBar.tintColor = UIColor.white
sc.searchBar.delegate = self
return sc
}()
func setupNavBarButtons() {
let searchBarButtonItem = UIBarButtonItem(title: "Search", style: .plain, target: self, action: #selector(handleSearch))
navigationItem.rightBarButtonItem = searchBarButtonItem
setupFilterButton()
}
func setupFilterButton() {
let containerView = UIView()
containerView.frame = CGRect(x: 0, y: 0, width: 100, height: 40)
containerView.addSubview(filterButton)
filterButton.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
filterButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
let filterBarButtonItem = UIBarButtonItem(customView: containerView)
navigationItem.leftBarButtonItem = filterBarButtonItem
}
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupNavBarButtons()
view.backgroundColor = .white
}
func handleSearch() {
searchController.searchBar.isHidden = false
navigationItem.titleView = searchController.searchBar
searchController.searchBar.becomeFirstResponder()
navigationItem.rightBarButtonItems = nil
navigationItem.leftBarButtonItems = nil
// changed!
filterBarHeightLC?.constant = 0
}
func setupViews() {
view.addSubview(filterBar)
view.addSubview(tableView)
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: filterBar.bottomAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
filterBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
filterBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
filterBar.topAnchor.constraint(equalTo: view.topAnchor, constant: 64).isActive = true
// changed!
filterBarHeightLC = filterBar.heightAnchor.constraint(equalToConstant: 40)
filterBarHeightLC?.isActive = true
}
extension ViewController: UISearchControllerDelegate, UISearchResultsUpdating, UISearchBarDelegate {
func filteredContentForSearchText(_ searchText: String, scope: String = "All") {
tableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
setupNavBarButtons()
searchController.searchBar.isHidden = true
// changed!
filterBarHeightLC?.constant = 40
}
func updateSearchResults(for searchController: UISearchController) {
filteredContentForSearchText(searchController.searchBar.text!)
}
}
Have a fun coding! :)

Resources