UISearchController - Black Rectangle - swift

I'm implementing a simple tableViewController with UISearchcontroller.
The problem is that every time that I press the search field then a black rectangle appear right after the keyboard shows up.
I also tried to use definesPresentationContext and searchBarStyle but it keep showing the rectangle. On the other hand, it looks like doesn't happen in the simulator since there is no keyboard.
Update: Below some photos.
ViewController:
class ListGlobalViewController: UITableViewController, StoryboardSceneBased, ViewModelBased {
static var sceneStoryboard: UIStoryboard = UIStoryboard(name: "DataSelectorViewController", bundle: Bundle.main)
// --------------------
// MARK: - Properties
// --------------------
var viewModel: ListGlobalViewModel!
private let disposeBag = DisposeBag()
private let searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.searchBarStyle = .minimal
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = true
return searchController
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
configure(with: viewModel)
navigationItem.largeTitleDisplayMode = .automatic
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.searchController.isActive = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
// --------------------
// MARK: - Functions
// --------------------
func configure(with viewModel: ListGlobalViewModel) {
self.tableView.delegate = nil
self.tableView.dataSource = nil
viewModel.outputs.listDataObservable.bind(to: self.tableView.rx.items(cellIdentifier: "listGlobalCell", cellType: ListGlobalCell.self)) { (index, model, cell) in
cell.model = model
}.disposed(by: disposeBag)
searchController.searchBar.rx.text.filterNil().throttle(1, scheduler: MainScheduler.instance).distinctUntilChanged().subscribe(viewModel.inputs.searchBarObservable).disposed(by: disposeBag)
}
}
Navigation Default Values:
let controller = UINavigationController()
controller.navigationBar.isTranslucent = false
controller.navigationBar.prefersLargeTitles = true
controller.definesPresentationContext = true
controller.navigationBar.titleTextAttributes = [ NSAttributedString.Key.font: UIFont.bold(size: 24), NSAttributedString.Key.foregroundColor: UIColor.black ]
if #available(iOS 11, *) {
controller.navigationBar.largeTitleTextAttributes = [ NSAttributedString.Key.font: UIFont.bold(size: 33), NSAttributedString.Key.foregroundColor: UIColor.black ]
}
return controller
Pictures:
Bugged

Use UITextField instead if UISearchBar. You can give any design to uitextfield. UISearch bar has limited designable attributes. Also you can reach any search function with textfield.

Related

Child UITableViewController does not scroll up when keyboard appears

I have searched as many posts as I found and still I cannot solve the problem. Please keep in mind that I have tried all the solutions provided that I have found and still when the keyboard appears the table does not scroll up, nor does it adjust its content view. The table v.c. is a child of a v.c., it is being pushed by the nav con (modal also does not work), below all of this sits a tab bar controller. Below is the code which I consider to be relevant.
Code inside the v.c.:
override func viewDidLoad() {
super.viewDidLoad()
safeGuide = self.view.safeAreaLayoutGuide
view.backgroundColor = UIColor.customColoursForAllElements(colourName: "background blue")
programMainMenu = self.tabBarController as? ProgramMainMenu
setTopViews()
let msgsArea = ChatMsgsAreaTVC(typeForCurrentInstance: .singleChat(otherMemberName: self.otherMemberName), userUID: self.userUID, searchResultMsg: msgSelectedInSearch != nil ? msgSelectedInSearch : nil, searchTxt: self.searchTxt)
msgsArea.valuesForSingleChat = chatDetails
displayChild(controller: msgsArea)
}
fileprivate func displayChild(controller: UIViewController) {
self.addChild(controller)
self.view.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
let childConstraints = [
controller.view.topAnchor.constraint(equalTo: detailsContainer.bottomAnchor, constant: 10),
controller.view.bottomAnchor.constraint(equalTo: safeGuide.bottomAnchor, constant: -55),
controller.view.leadingAnchor.constraint(equalTo: backBtn.leadingAnchor),
controller.view.trailingAnchor.constraint(equalTo: optionBtn.trailingAnchor)
]
NSLayoutConstraint.activate(childConstraints)
controller.didMove(toParent: self)
}
the code below is from the child table view controller:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidHide), name: UIResponder.keyboardDidHideNotification, object: nil)
configureMsgsTable()
observeAudioRecAccess()
setupAudioRecorder()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.resignFirstResponder()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.main.async {self.becomeFirstResponder()}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
override var canBecomeFirstResponder: Bool {return true}
override var canResignFirstResponder: Bool {return true}
#objc func keyboardWillShow(notification: Notification) {
// note: changing the content size of the table does not work at all.
}
#objc fileprivate func keyboardDidHide() {
if !bypassForSetup {bypassForSetup = true}
else {
if msgTxtArea.isFirstResponder {msgTxtArea.resignFirstResponder()}
if addFileToMsgBtn.isSelected {addFileToMsgBtn.isSelected = false; addFileToMsgBtn.resignFirstResponder(); alterFileToMsgBtn(selected: false)}
}
}
fileprivate func configureMsgsTable() {
tableView.contentInsetAdjustmentBehavior = .always
tableView.backgroundColor = .clear
tableView.keyboardDismissMode = .interactive
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.estimatedRowHeight = 100
tableView.sectionFooterHeight = 0.0
tableView.scrollsToTop = false
tableView.setBottomInset(to: -15)
}
I also have a custom input accessory view that I have not included in this code.

Swift: deallocate modally presented view controller

I have a modally presented SearchviewController that contains a UISearchController.
When swiping down it gets deallocated, but only if the searchControllers searchBar is not in editing mode. Only if I press its cancel button in advance, it gets deallocated.
How can I make sure it gets deallocated, even when in editing mode? There are definitely no strong self references within any closures...
Presenting ViewController:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addButton()
}
func addButton() {
let mediumConfiguration = UIImage.SymbolConfiguration(scale: .large)
var checkButtonImage = UIImage(systemName: "plus", withConfiguration: mediumConfiguration)
checkButtonImage = checkButtonImage?.withTintColor(.label)
let button = UIButton(type: .contactAdd)
button.addTarget(self, action: #selector(onAddViewControllerButtonClicked(sender:)), for: .touchUpInside)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
}
#objc func onAddViewControllerButtonClicked(sender: UIButton) {
let viewController = SearchViewController()
viewController.view.backgroundColor = .secondarySystemBackground
let navigationController = UINavigationController()
navigationController.viewControllers = [viewController]
self.present(navigationController, animated: true)
}
}
Presented ViewController:
class SearchViewController: UIViewController, UISearchBarDelegate, UISearchResultsUpdating {
override func viewDidLoad() {
super.viewDidLoad()
configureSearchController()
}
var searchController: UISearchController?
func configureSearchController() {
//search
searchController = UISearchController(searchResultsController: nil)
searchController?.searchResultsUpdater = self
searchController?.searchBar.delegate = self
searchController?.hidesNavigationBarDuringPresentation = false
searchController?.searchBar.searchBarStyle = .minimal
searchController?.searchBar.keyboardType = .webSearch
self.navigationItem.searchController?.searchBar.backgroundColor = .clear
self.navigationItem.searchController = searchController
self.navigationItem.hidesSearchBarWhenScrolling = false
self.definesPresentationContext = true
self.navigationItem.searchController = searchController
}
func updateSearchResults(for searchController: UISearchController) {
return
}
//check deallocation
deinit { print("\(NSStringFromClass(type(of: self))): deallocated") }
}
Can you help with that?
Thank you in advance!
Adding
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.navigationItem.searchController = nil
}
to SearchViewController fixes the problem for me, but admittedly I have no idea as to why this is necessary.

Problem with delegates removing annotation

I have two screens. The first one (firstViewController) has a mapView with a UITapGestureRecognizer. When the user taps the screen, an annotations is added to the map and the second screen (secondViewController) is presented.
When the user dismisses the secondViewController and comes back to the first one, the annotation should be removed. I know I have to use delegation, but I just can't make it to work.
This is the code I have now:
class firstViewController: UIViewController, AnnotationDelegate {
let mapView = MKMapView()
var temporaryPinArray = [MKPointAnnotation]()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mapView
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
mapView.addGestureRecognizer(gesture)
secondVC.annotationDelegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
mapView.frame = view.bounds
}
#objc func handleTap(_ gestureReconizer: UILongPressGestureRecognizer) {
let location = gestureReconizer.location(in: mapView)
let coordinates = mapView.convert(location, toCoordinateFrom: mapView)
mapView.removeAnnotations(mapView.annotations)
let pin = MKPointAnnotation()
pin.coordinate = coordinates
temporaryPinArray.removeAll()
temporaryPinArray.append(pin)
mapView.addAnnotations(temporaryPinArray)
// Present secondViewController
let secondVC = SecondViewController()
panel.set(contentViewController: secondVC)
panel.addPanel(toParent: self)
}
func didRemoveAnnotation(annotation: MKPointAnnotation) {
mapView.removeAnnotation(annotation)
}
}
Second View Controller
protocol AnnotationDelegate {
func didRemoveAnnotation(annotation: [MKPointAnnotation])
}
class SecondViewController: UIViewController {
var annotationDelegate: AnnotationDelegate!
let mainVC = firstViewController()
let closeButton: UIButton = {
let button = UIButton()
button.backgroundColor = .grey
button.layer.cornerRadius = 15
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(closeButton)
closeButton.addTarget(self, action: #selector(dismissPanel), for: .touchUpInside)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
closeButton.frame = CGRect(x: view.frame.width-50, y: 10, width: 30, height: 30)
}
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray)
}
}
Thank you so much for your help!
You created a new instance of firstViewController inside SecondViewController. This instance is unrelated to the actual first one:
let mainVC = firstViewController()
This means that temporaryPinArray is different as well. So instead of passing in this unrelated array...
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray)
}
Just change the function to take no parameters instead:
protocol AnnotationDelegate {
func didRemoveAnnotation() /// no parameters
}
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation() /// no parameters
}
And inside firstViewController's didRemoveAnnotation, reference the actual temporaryPinArray.
func didRemoveAnnotation() {
mapView.removeAnnotations(temporaryPinArray) /// the current array
}

When Click on Navigation Bar Tableview scroll with Refresh Control in Swift 4 iOS

When I click on NavigationBar, my tableview is scrolled but the issue is it also scroll refresh control ! How can I scroll my tableview only, but not RefreshControl?
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
let newHotel = Hotels(name: "Montage Laguna Beach", place: "California south")
hotels.append(newHotel)
hotels.sort() { $0.name < $1.place }
self.tableView.reloadData()
self.refreshControl.endRefreshing()
}
override func viewDidLoad() {
super.viewDidLoad()
self.setupSearchBar(searchController: searchController, tableView: tableView)
self.tableView.addSubview(self.refreshControl)
}
func setupSearchBar(searchController: UISearchController, tableView: UITableView){
searchController.searchResultsUpdater = self as? UISearchResultsUpdating
searchController.searchBar.tintColor = UIColor.white
if #available(iOS 9.1, *) {
searchController.obscuresBackgroundDuringPresentation = false
} else {
}
definesPresentationContext = true
searchController.searchBar.placeholder = "Search"
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = true
navigationItem.searchController = searchController
} else {
tableView.tableHeaderView = searchController.searchBar
}
searchController.searchBar.delegate = self as? UISearchBarDelegate
}
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(ViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
refreshControl.tintColor = UIColor.red
return refreshControl
}()

UISeachController Delegate methods not called, searchbar can't become first responder

I'm having some difficulty getting the searchbar in my searchcontroller to become firstResponder. I've noticed the delegate methods are not being called, but the searchbar works as intended when I'm typing to filter a list of users.
Definition of searchcontroller:
lazy var searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search"
return searchController
}()
Setting it up:
private func setupSearchController() {
self.navigationItem.searchController = searchController
searchController.definesPresentationContext = true
searchController.delegate = self
searchController.isActive = true
searchController.searchBar.delegate = self
searchController.searchBar.becomeFirstResponder()
}
I tried this suggestion from another SO question but the delegate method isn't being called:
func didPresentSearchController(searchController: UISearchController) {
UIView.animate(withDuration: 0.1, animations: { () -> Void in }) { (completed) -> Void in
searchController.searchBar.becomeFirstResponder()
}
}
The problem is you are trying to access UI element(searchbarcontroller) before UI is completely loaded.
This can be done in 2 ways
Use main queue to show keypad
private func setupSearchController() {
self.navigationItem.searchController = searchController
searchController.definesPresentationContext = true
searchController.delegate = self
searchController.isActive = true
searchController.searchBar.delegate = self
DispatchQueue.main.async {
self.searchController.searchBar.becomeFirstResponder()
}
}
With this approach keypad will be only show at one time in viewDidLoad
Show keypad in viewDidAppear
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
self.searchController.searchBar.becomeFirstResponder()
}
}
With this approach keypad will be always shown whenever screen appears.