How to show different searchResultsController in UISearchController - swift

I am using UISearchController (not UISearchDisplayController), i wanted to show a separate view when search is active. I dont want to use the current view.
Tried with self.searchController = UISearchController(searchResultsController: self.resultsController ). But this doesn't show anything.
Even tried this
func presentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
searchController.searchResultsController?.view.isHidden = false
}
}
func didPresentSearchController(_ searchController: UISearchController) {
searchController.searchResultsController?.view.isHidden = false
}
My default table data before search is active is as below, i want this table to hide and show separate view and pass the typed keyword to that new view. How can i achieve this?

As there is no direct segue to viewController, we need to explicitly refer the viewController we wanted to use.
I have used the below code and it loads my new controller when my searchBar is active.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
self.resultsController = storyboard.instantiateViewController(withIdentifier: "searchResults") as! SearchResultsTableViewController
self.searchController = UISearchController(searchResultsController: self.resultsController )
Let me know if any better approach is available.

Related

Open first View based on condition

I want my app to check at start conditionaly if a variable is correct or not. Based on that it should either go to an intro screen (where he can select a variable in my case select a team) or it should start the main view. After searching i found this code and edited it.
But there still seems to be problems. First of all I dont have two identifier. The Intro has one but not the main view. My main View is called WeatherViewController and the Intro screen is called FirstScreenViewController.
I also added a picture of my Main.storyboard.
I also googled a lot about conditional UINavigationController but I can only understand with a video and did not found a video about it.
I tried to use the code from here.
var id = hello ? "goToIntro" : "???"
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let WeatherViewController: UIViewController = mainStoryboard.FirstScreenViewController(withIdentifier: WVC has no identifier??) as UIViewController
self.window?.rootViewController = WeatherViewController
self.window?.makeKeyAndVisible()
if hello {
self.performSegue(withIdentifier: "goToIntro", sender: self)
} else {
/here nothing should happen. It should open the Main View
self.performSegue(withIdentifier: "???", sender: self)
}
Simply repeat the steps you did for creating the segue goToIntro.
Add a new UIViewController (MainViewController) in your storyboard.
Add a segue and give it an identifier.
Provide the identifier value in the else condition of the code that you've shown in your post to perform the segue to the UIViewController that you've just created.
The final code should look something like this:
var id = hello ? "goToIntro" : "goToMain"
guard let windowScene = scene as? UIWindowScene else { return }
window = UIWindow(windowScene: windowScene)
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let weatherViewController = mainStoryboard.instantiateInitialViewController()
window?.rootViewController = weatherViewController
window?.makeKeyAndVisible()
weatherViewController.performSegue(withIdentifier: id, sender: self)
You don't need the if else block since the id is already determined using the ? ternary operator.

Mixed programmatic and storyboard. Button click will not open next VC in a navigation controller

I have a tab bar controller that is built programatically. And it looks something like this:
class NewTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
createTabbar()
}
func createTabbar() {
let deliveryViewController = storyBoard.instantiateViewController(identifier: "DeliveryViewController") as? DeliveryViewController
deliveryViewController?.tabBarItem.image = #imageLiteral(resourceName: "icon_deliver")
deliveryViewController?.title = "Delivery"
planDictionary["planType"] = Constants.Mealplan.deliveryPlan
deliveryViewController?.planDictionary = planDictionary
// Excluded other tabs and view controller creations since they are the same
}
Now this DeliveryViewController is created in storyboard and embedded in a nav controller
It has a button click action:
#IBAction func saveNameButton(_ sender: UIButton) {
let addressViewController = storyBoard.instantiateViewController(identifier: "AddressViewController") as? AddressViewController
addressViewController?.planDictionary = planDictionary
navigationController?.pushViewController(addressViewController!, animated: true)
}
The button click action was working when the tabbar was in the storyboard. But after refactoring programatically, it is does not place the next VC on the navigation stack.
Help would be appreciated.
Almost certainly...
This line:
let deliveryViewController = storyBoard.instantiateViewController(identifier: "DeliveryViewController") as? DeliveryViewController
Is instantiating an instance of DeliveryViewController and setting that as the View Controller for a Tab. But what you want to do is load a UINavigationController and make DeliveryViewController the root view controller of that NavController, and set that NavController as a Tab item.

Change ViewController from Tabbar

I have a UITabBarController with 3 tabs and a SettingsVC where you can choose the appearance of MainVC (There are 2 different ViewControllers which based on what user prefers should be shown as the MainVC).
I made the application and it works but it's really buggy, cause I was just pushing the ViewControllers on top of each other, and in some certain conditions you see bugs while switching tabs.
Main.storyboard
Here is some part of my code(after trying a lot of stuff) which changes the VC's based on UserDefaults
CheckSettingsStatus.swift
//
// Check for Double Column Settings and navigate to Double Column VC
//
func checkMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 1{
//let appDel = UIApplication.shared.delegate as! AppDelegate
//appDel.goToDoubleMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "DoubleColumnMainVC") as! DoubleColumnMainVC
self.navigationController?.viewControllers[0] = mainVC
//navigationController?.pushViewController(mainVC, animated: false)
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
self.navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
//
// same as function above but goes to MainVC
//
func checkDoubleColumnMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 0{
// let appDel = UIApplication.shared.delegate as! AppDelegate
// appDel.goToMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "MainVC") as! MainVC
self.navigationController?.viewControllers[0] = mainVC
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
With this in ViewWillAppear of both this Controllers I call these functions to switch accordingly.
Any ideas what I'm doing wrong? Can I push controllers from UITabBarController and not NavigationController? something like:
tabBarController.pushViewController(mainVC, animated: false)
instead of:
navigationController.pushViewController(mainVC, animated: false)
Thank you in advance
UPDATE:
In my best case when everything works nicely, on first launch of the app (to the second mainVC) the Buttons in NavBar doesn't work.
EDIT:
I just realised that the NavigationBar that I'm seeing above my SecondMainVC is the NavigationBar from MainVC, that is why the buttons are not working. how is that possible?
I faced same issue while trying to develop a tab bar controller in storyboard , for this reason I found that the best way is to implement it by code.
so you can make your code look like this :
1) create a a sub class of UITabController and let it conform to UITabBarControllerDelegate
class TabBarVC: UITabBarController , UITabBarControllerDelegate {
2)then write this method in your class to initialize the tabs in your project:
private func settingUpTabBarAndInitializingViewControllers(){
//self delagting
self.delegate = self
//home tab
let homeStoryboard = UIStoryboard(name: Constants.StoryBoards.homeStoryboard, bundle: nil)
let homeVC = homeStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.homeScreen) as! HomeVC
// shop tab
let shopStoybaord = UIStoryboard(name: Constants.StoryBoards.shopStoryboard, bundle: nil)
let shopVC = shopStoybaord.instantiateViewController(withIdentifier: Constants.ControllersIDs.shopScreen) as! ShopVC
//offers tab
let offersStoryboard = UIStoryboard(name: Constants.StoryBoards.offersStoryboard, bundle: nil)
let offersVC = offersStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.offersScreen) as! OffersVC
//more tab
let moreStoryboard = UIStoryboard(name: Constants.StoryBoards.MoreScreenStoryboard, bundle: nil)
let moreVC = moreStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.moreVCScreen) as! MoreOptionsTVC
//setting VCs
self.viewControllers = [homeVC , shopVC , offersVC , moreVC]
//setting buttons
//buttons images
let homeImage = UIImage(named: "TabHome")
let shopImage = UIImage(named : "TabShop")
let offerImage = UIImage(named: "TabOffers")
let moreImage = UIImage(named: "TabMenu")
// buttons shakhsiyan
let homeButton = UITabBarItem(title: homeText, image: homeImage, selectedImage: homeTappedImage)
let shopButton = UITabBarItem(title: shopText, image: shopImage, selectedImage: shopTappedImage)
let offersButton = UITabBarItem(title: offersText, image: offerImage, selectedImage: offersTappedImage)
let moreButton = UITabBarItem(title: moreText, image: moreImage, selectedImage: moreTappedImage)
homeVC.tabBarItem = homeButton
shopVC.tabBarItem = shopButton
offersVC.tabBarItem = offersButton
moreVC.tabBarItem = moreButton
}
P.S. you can add tabs as much as you want and while scrolling in them you wont face any bug,
make sure to have only one navigation controller before your tab bar controller.
Check this hope it helps
Ok you can create a DEMO to see how thing works.After that you can implement it in your project.I know it may not look good to you but read it once and see image attached i am sure will get your answer.
1.I have created two UIViewControllers with the UITabBarController.
2.I have created class ItemFirstVC,ItemTwoVC and assigned to UIViewControllers in storyboard
3.Each controller is embedded in UINavigationController
4.Create a custom class of UITabBarController
5.Assign it to UITabBarController in storyboard
6.Now Each UIViewController has a button named as Firs Button,Second Button
7.From each button i have connected Action Segue to one more controller as you can see in image.
8.Now i have created a third UIViewController which is separate from UITabBarController.
9.Now we will load that Third UIViewController in UITabBarController
10.Create one more class ItemThirdVC and assign it to third UIViewController.
11.It also has a button named as third button action segued to one more UIViewController
12.Now switch to custom class of UITabBarContoller
// variable required to see work in action
var count = 0
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// remember to assign delegate
delegate = self
}
// this delegate indicates whether this tab should be selected or not
extension CustomTabBarController : UITabBarControllerDelegate{
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// switch to UIViewController tabs multiple times
count = count + 1
if(count == 5){
// when count reaches 5 a new controller will load
let navigationController = viewController as? UINavigationController
guard navigationController != nil else {return true}
let viewController = storyboard?.instantiateViewController(withIdentifier: "ItemThreeVC")
guard let vc = viewController else {return true}
navigationController?.setViewControllers([vc], animated: false)
}
return true
}
}

How to access a function in another class being within the same instance?

I am currently creating a login project in Swift 3.
I have a main view controller which contains
a container, which includes a page viewcontroller and multiple pages
a custom "menu" at the bottom which includes two buttons for navigating the page view controller and a custom page control
This is my storyboard setup
The view the user will be presented with is made of the pages controlled by the page viewcontroller, at the top, and the control menu, at the bottom. When the user clicks on the buttons in the control menu this code will be executed in the main viewcontroller:
#IBAction func buttonNext(_ sender: UIButton) {
print("buttonNext has been pressed!")
nextPageWithIndex(index: nextIndex)
}
and here is the function it calls:
func nextPageWithIndex(index: Int) {
let nextWalkthroughVC = pages[index]
pageContainer.setViewControllers([nextWalkthroughVC], direction: .forward, animated: true, completion: nil)
print("nextPageWithIndex has been executed with Index \(index)!")
if currentIndex as Int? == 2 {
currentIndex = 2
} else if currentIndex as Int? == 1 {
currentIndex = 2
} else if currentIndex as Int? == 0 {
currentIndex = 1
} else {
currentIndex = 0
print("currentIndex was in inpossible state!")
}
checkButtonNavigation()
assignIndexes()
if let index = currentIndex {
pageControl.currentPage = index
print("PageControl updated")
}
}
checkButtonNavigation() checks if the buttons at the bottom should be displayed depending on the page which is currently being displayed.
I am trying to implement a button of one of the pages (in this case page1) which navigates the user to the next page.
I tried to execute the function nextPageWithIndex(index: 1) with varAccountRegisterMainViewController.nextPageWithIndex(index: 1) on the action of pressing the button in the page1 viewcontroller class by defining:
var varAccountRegisterMainViewController: accountRegisterMainViewController!
in the AppDelegate,
and:
varAccountRegisterMainViewController = accountRegisterMainViewController()
in the page1 viewcontroller.
Sadly that doesn't seem to work. I get "fatal error: Index out of range" at the point:
let nextWalkthroughVC = pages[index]
in the function nextPageWithIndex(index: 1) in the main viewcontroller. I checked what count the array "pages" has. It's 0. When I execute the function right from the main viewcontroller, though, it has 3 as it should have. Here is the error as an image.
Thank you for your help!
EDIT:
here is how the pages Array is set-up:
At the beginning of the main viewcontroller class the following code is added:
var pages = [UIViewController]()
In the viewDidLoad() that code:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let page1: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage1ViewController")
let page2: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage2ViewController")
let page3: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage3ViewController")
pages.append(page1)
pages.append(page2)
pages.append(page3)
ANOTHER EDIT:
I put the declaration of "pages" also into the externalNext() function. Now that problem is solved but there is another one... this is how the function looks now:
func externalNext(index: Int) {
var pageContainer: UIPageViewController!
var pages = [UIViewController]()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let page1: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage1ViewController")
let page2: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage2ViewController")
let page3: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage3ViewController")
pages.append(page1)
pages.append(page2)
pages.append(page3)
// Create the page container
pageContainer = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
pageContainer.delegate = self
pageContainer.dataSource = self
pageContainer.setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
// Add it to the view
view.addSubview(pageContainer.view)
pageContainer.view.addSubview(pageControlView)
print("Current array count of pages is \(pages.count)")
print("Function: nextPageWithIndex has been called with index \(index)")
let nextWalkthroughVC = pages[index]
pageContainer.setViewControllers([nextWalkthroughVC], direction: .forward, animated: true, completion: nil)
print("nextPageWithIndex has been executed with Index \(index)!")
pageControl.currentPage = index
print("PageControl updated")
print("Hello")
}
I copied the code from the top of the main viewcontroller class and just pasted it into the function. I alway get the error "fatal error: unexpectedly found nil while unwrapping an Optional value", though.
Is there any way I can redefine the objects in the function?
Or is there any elegant way? My way looks very messy...
Thanks to Phillip Mills I know that is has something to do with being another instance of the class main viewcontroller. How can I access the function in there but within the same instance? The main viewcontroller is visible all the time.
Thank you!
When you do this: accountRegisterMainViewController() you are creating a new object, a different instance of that class. Since the new one is not being displayed, viewDidLoad is never called and your array is empty.
You need to arrange links between objects so that the one loaded from your storyboard is also the one that nextPageWithIndex is called on.

Change the style of the UISearchController views?

Question:
How do I change the color/theme/style of the UISearchController in tvOS?
( Would appear I need to set UIStatusBarStyle some way, since the preferredStatusBarStyle override does not exist in tvOS for UISearchController)
Description:
Method called in my AppDelegate to programmatically create a searchNavigationController with on top a UISearchController and on bottom my custom UITableViewController called "searchResultsController" (SearchViewController)
func configueSearchController() -> UIViewController {
let storyboard = UIStoryboard(name: "Search", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchViewController.storyboardIdentifier) as? SearchViewController else {
fatalError("Unable to instatiate a SearchResultViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. Gastric Bypass)", comment: "")
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer = UISearchContainerViewController(searchController: searchController)
searchContainer.title = NSLocalizedString("Search", comment: "")
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
return searchNavigationController
}
Image of the visual representation of the above method:
What I've tried:
I've attempted to change the style via different approaches and none of them gave the desired outcome.
This has no effect on the searchBar:
searchController.searchBar.searchBarStyle = .minimal //or .prominent or .default
This only makes the searchBar (actual input area black.. with black text..):
searchController.searchBar.backgroundColor = .black
This only makes the whole background view of the UISearchController black. Everything is black and thus the keyboard can not be seen.
searchController.view.backgroundColor = .black
The ideal solution would be a theme change from the standard .default to something .dark. Because not only does the background color have to change but the borders and text colors must too.
Attempted to implement this solution:
//UISearchController
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
However, this override does not seem to exist for tvOS. Any reason why?
For all those wanting to achieve this on tvOS with a TabBarController and storyboard, what I did was add a container view to my view controller and added the tab bar controller as a child.
class SearchController: UIViewController {
#IBOutlet var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController.init(searchResultsController: nil)
searchController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
searchController.view.frame = containerView.bounds
let searchContainer: UISearchContainerViewController = UISearchContainerViewController(searchController: searchController)
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
searchNavigationController.navigationBar.isTranslucent = true
searchNavigationController.navigationBar.tintColor = .black
searchNavigationController.tabBarItem.title = "Search"
containerView.addSubview(searchNavigationController.view)
searchNavigationController.didMove(toParent: self)
}
}
There is a work around to this problem, however I don't really understand why they don't keep the same system as for iOS. Anyway, the main things to do to change the SearchBar style to black is to set the keyboardAppearance to dark, the searchController's view's background color and to set the searchBar's background color to white.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
guard let win = window else {
return true
}
if let tabController = win.rootViewController as? UITabBarController {
tabController.viewControllers?.append(configueSearchController())
}
win.backgroundColor = UIColor.white
win.makeKeyAndVisible()
return true
}
func configueSearchController() -> UIViewController {
let storyboard = UIStoryboard(name: "Search", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchViewController.storyboardIdentifier) as? SearchViewController else {
fatalError("Unable to instatiate a SearchResultViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. Gastric Bypass)", comment: "")
searchController.view.backgroundColor = UIColor.black
searchController.searchBar.keyboardAppearance = UIKeyboardAppearance.dark
searchController.searchBar.tintColor = UIColor.black
searchController.searchBar.backgroundColor = UIColor.white
searchController.hidesNavigationBarDuringPresentation = false
searchController.obscuresBackgroundDuringPresentation = true
searchController.searchBar.searchBarStyle = .minimal
searchController.searchBar.sizeToFit()
//searchResultsController.tableView.tableHeaderView = searchController.searchBar
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer: UISearchContainerViewController = UISearchContainerViewController(searchController: searchController)
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
searchNavigationController.navigationBar.isTranslucent = true
searchNavigationController.navigationBar.tintColor = .black
searchNavigationController.tabBarItem.title = "Search"
return searchNavigationController
}
I am not an expert on tvOS, but here's what I've found.
I started with the UIKit Catalog: Creating and Customizing UIKit Controls example code from Apple. The first thing that I noticed is that the code you supplied above instantiates the searchResultsController as a SearchViewController, whereas Apple's example code instantiates the searchResultsController as a SearchResultsViewController. I assume you meant to do this.
After playing around with the example code, I was able to make very limited style changes. Here is a screenshot of the closest I have come to what it seems you are trying to achieve.
And here is the code that affected that change.
func packagedSearchController() -> UIViewController {
// Load a `SearchResultsViewController` from its storyboard.
let storyboard = UIStoryboard(name: "ViewControllerSamples", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchResultsViewController.storyboardIdentifier) as? SearchResultsViewController else {
fatalError("Unable to instatiate a SearchResultsViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. iceland)", comment: "")
//** This is the part you are interested in **//
searchResultsController.view.backgroundColor = .black
searchController.view.backgroundColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
searchController.searchBar.backgroundColor = .white
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer = UISearchContainerViewController(searchController: searchController)
searchContainer.title = NSLocalizedString("Search", comment: "")
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
return searchNavigationController
}
Unfortunately, I was unable to set the SearchController's view to .black without loosing the keyboard in the darkness. I looked and looked for a solution to the disappearing keyboard problem thinking that there must be a way to style the keyboard, or at least modify the selection state, all to no avail. My disappointment culminated in this statement from the App Programming Guide for tvOS: Designing the Keyboard Experience;
An inline keyboard displays all of the possible inputs on a single line. Use UISearchController to create a keyboard that can be fully integrated with third-party content. However, there are very few customization options when you use UISearchController. You can not access the text field itself, customize the traits, or add input accessories.
I suspect that if you could get a reference to that keyboard, you may be able to affect some change. That being said, I have not been able to find a real solution to date. Maybe you could attach a custom inputAccessoryViewController to the SearchController and effect a change that way?
Sorry I couldn't be more helpful.