Search bar when swiping down in swift - swift

I am developing a social media app for iOS
My ViewControllers are currently embedded in a NavigationController.
On my news feed screen, I need to display a search bar when the user swipes down (and hide it when he swipes up) in which if the user types something, the search results will be displayed on top of the news feed screen.
I have tried to tinker with this but I am pretty new to iOS and so far have not managed to get this to work.
Any help would grately be appreciated and keep in mind that I have only been programming iOS for a couple of weeks so some in-depth would be helpful.
Thank you !

First, you'll need to implement UISwipeGestureRecognizer
include the setup() function in viewDidAppear
func setup() {
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(down))
swipeDown.direction = .down
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(up))
swipeUp.direction = .up
self.view.addGestureRecognizer(swipeDown)
self.view.addGestureRecognizer(swipeUp)
searchBar = UISearchBar(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.size.width, height: 40.0))
if let searchBar = searchBar
{
searchBar.backgroundColor = UIColor.red
self.view.addSubview(searchBar)
}
}
Then your two functions up and down
func down(sender: UIGestureRecognizer) {
print("down")
//show bar
UIView.animate(withDuration: 1.0, animations: { () -> Void in
self.searchBar!.frame = CGRect(x: 0.0, y: 64.0, width: self.view.frame.width, height: 40.0)
}, completion: { (Bool) -> Void in
})
}
func up(sender: UIGestureRecognizer) {
print("up")
UIView.animate(withDuration: 1.0, animations: { () -> Void in
self.searchBar!.frame = CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: 40.0)
}, completion: { (Bool) -> Void in
})
}
You can add Bool isShowing to avoid unnecessary animations. Then, implement the search bar delegate textDidChange to change the search results as the user types.
func searchBar(_ searchBar: UISearchBar,textDidChange searchText: String)`
All you need to do now is display your results in a UISearchController.
Note
Using swipe up/down motion might interfere with the scrolling of the UIScreachController

Related

Animations with safeAreaLayoutGuide (Swift 5)

I would like a smooth animation of this view whenever the search bar is selected and deselected. Right now it's choppy:
Heres my code below in the searchResultsUpdater. From what I understand, these functions should handle the animations, I'm not sure what's wrong here:
func updateSearchResults(for searchController: UISearchController) {
//MapView moves up when search bar is selected
if searchController.isActive == true{
UIView.animateKeyframes(withDuration: 0.25, delay: 0.0, options: UIView.KeyframeAnimationOptions(rawValue: 7), animations: {
self.mapView.frame.origin.y=self.view.safeAreaLayoutGuide.layoutFrame.origin.y
},completion: nil)
}
//MapView moves down when cancel button is selected
if searchController.isActive == false{
UIView.animateKeyframes(withDuration: 0.25, delay: 0.0, options: UIView.KeyframeAnimationOptions(rawValue: 7), animations: {
self.mapView.frame.origin.y=self.view.safeAreaLayoutGuide.layoutFrame.origin.y
},completion: nil)
}
}
Any help is appreciated, thank you!
I found the solution. Originally I had the CGRect for the mapView frame in viewDidLayoutSubviews():
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//layout to fit frame of view
mapView.frame = CGRect(x: 0, y: view.safeAreaInsets.top, width:
view.frame.size.width, height: view.frame.size.height -
view.safeAreaInsets.top)
}
I moved it to viewDidLoad(), and now it works:
override func viewDidLoad() {
super.viewDidLoad()
//add gesture recognizer for mapView
mapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(mapViewAnimation)))
//layout to fit frame of view
mapView.frame = CGRect(x: 0, y: view.safeAreaInsets.top, width:
view.frame.size.width, height: view.frame.size.height -
view.safeAreaInsets.top)
}
I also added a gesture recognizer instead so that the searchResultsUpdater wouldn't be so cluttered:
//handle the animation for mapview
#objc func mapViewAnimation(){
UIView.animateKeyframes(withDuration: 0.25, delay: 0.0, options:
UIView.KeyframeAnimationOptions(rawValue: 7), animations: {
self.mapView.frame.origin.y =
self.view.safeAreaLayoutGuide.layoutFrame.origin.y
},completion: nil)
}
If anyone knows why it didn't work when I had the CGRect in didLayoutSubViews() feel free to let me know. Thanks!

Animating a class that is an extension of UIView in Swift

I'm trying to get an instance of my Options class to animate onto the screen using the function appear() which activates when pressing a button in my SearchResult class but I'm having trouble, the instance has been added as a subview to the view in the ViewController Class.
I've been able to use the function optionMenu.appear() in ViewController and that works as it should but not when it's called through clicking the button in SearchResult, it seems to do everything but the animation as I've tested printing in optionMenu.appear() and it successfully prints.
import Foundation
import UIKit
class SearchResult {
private let link: String
var body = UIView()
init(_ link: String){
self.link = link
let options = UIButton(x: Int(body.bounds.width-60), y: 10, size: 50.0)
options.addTarget(self, action: #selector(optionsClicked), for: .touchUpInside)
body.addSubview(options)
}
#objc func optionsClicked(){
ViewController().optionMenu.appear()
}
}
import Foundation
import UIKit
class Options: UIView{
var buttons = [UIButton]()
private var link = String()
init(_ titles: [String]){
let height = ((titles.count+1)*65)+5
super.init(frame: CGRect(x: 0, y: Int(UIScreen.main.bounds.height)-height, width: Int(UIScreen.main.bounds.width), height: height))
var i = 0
for title in titles{
let button = UIButton(frame: CGRect(x: 5, y: 5+(i*65), width: Int(UIScreen.main.bounds.width)-10, height: 60))
button.setTitle(title, for: .normal)
buttons.append(button)
self.addSubview(button)
i += 1
}
let cancel = UIButton(frame: CGRect(x: 5, y: 5+(i*65), width: Int(UIScreen.main.bounds.width)-10, height: 60))
cancel.setTitle("cancel", for: .normal)
self.addSubview(cancel)
}
func appear(){
UIView.animate(withDuration: 0.2, animations: {
self.transform = CGAffineTransform(translationX: 0, y: -self.bounds.height)
})
}
#objc func disappear(){
UIView.animate(withDuration: 0.2, animations: {
self.transform = CGAffineTransform.identity
})
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
It's because you are constructing a new Viewcontroller, and calling appear on that Viewcontroller's option menu every time optionsClicked() is called. You need a reference to the viewController that has the option menu and do myViewController.optionMenu.appear()

Adding a search bar to ui table view on overlay

I have managed to create, a dark overlay once clicked a search icon and have managed to add a table uiview on the dark overlay but now want to add a search bar within that table view. I cant seem to figure it out as my code seems different to everyone else's examples. I am new to swift so my code probably isn't the cleanest. can i ask if someone can show me how to do this as i am out of ideas. I have posted my code below can someone please show me where i'm going wrong.
Many thanks
class SearchLauncher: NSObject {
let blackView = UIView()
let tableView = UITableView()
#objc func showSearch() {
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(tableView)
let height: CGFloat = 600
let y = window.frame.height - height
tableView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
blackView.frame = window.frame
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.tableView.frame = CGRect(x: 0, y: y, width: self.tableView.frame.width, height: self.tableView.frame.height)
}, completion: nil)
}
}
#objc func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.tableView.frame = CGRect(x: 0, y: window.frame.height, width: self.tableView.frame.width, height: self.tableView.frame.height)
}
}
}
override init() {
super.init()
//start doing something here maybe
}
}```
How did you try to add a search controller?
I would do it by adding an UISearchController as a tableHeaderView of your table view. First make sure to add UISearchResultsUpdating protocol to your class.
class SearchLauncher: NSObject, UISearchResultsUpdating
Then add the search bar to your table view
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.sizeToFit()
searchController.searchResultsUpdater = self
//you probably don't need this
//searchController.dimsBackgroundDuringPresentation = false
tableView.tableHeaderView = searchController.searchBar
And then, implement the updateSearchResults(for:_) delegate method
func updateSearchResults(for searchController: UISearchController) {
filteredTableData.removeAll(keepingCapacity: false)
//filter the table data by using searchController.searchBar.text!
//filteredTableData = ...
self.tableView.reloadData()
}

Swift - Open UIViewController from within slide-in menu

I have a slide-in menu that presents itself when I press the menu button in my navigation bar. Dismissing it can be accomplished by either tapping the background or the menu itself. Within this view I have several items, one of which is an user profile button. When I press this button, I want the menu to dismiss itself and then instantly open the user profile view controller. That's why I call handleDismiss() first and then set the view that needs to be opened
However, it keeps telling me that the view is not in the window hierarchy. I know what the problem is (view stack), but I somehow can't get it to work properly. How should I tackle this problem?
RootViewController -> HomeController (tabBar index 0) -> Slide-In Menu -> UserProfileController
Button to open the controller
profileCell.profileButton.addTarget(self, action: #selector(handleUserProfile(sender:)), for: .touchUpInside)
Functions
func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
func handleUserProfile(sender: UIButton) {
handleDismiss()
let userProfileController = UserProfileController()
openProfileController(userProfileController)
}
func openProfileController(_ controller: UIViewController) {
present(controller, animated: false, completion: nil)
}
I fixed the problem by setting a reference to the rootViewController in my menu.
HomeController
let menuLauncher = MenuLauncher()
func tappedMenu(sender: UIButton) {
menuLauncher.showMenu(self)
}
MenuController
func MenuLauncher() {
self.m_ParentViewController = nil
}
func showMenu(_ parentViewController: UIViewController) {
self.m_ParentViewController = parentViewController
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(MenuLauncherImageCell.self, forCellWithReuseIdentifier: cellIdImage)
collectionView.register(MenuLauncherInfoCell.self, forCellWithReuseIdentifier: cellIdInfo)
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(collectionView)
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: 350)
blackView.frame = window.frame
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.collectionView.frame = CGRect(x: 0, y: window.frame.height - 350, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}, completion: nil)
}
}
func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
func handleUserProfile(sender: UIButton) {
handleDismiss()
let userProfileController = UserProfileController()
self.m_ParentViewController?.present(userProfileController, animated: true, completion: nil)
}

Use nested function as action for `UIButton`

I'm trying to add a target to my UIButton, but get an error when trying to use a nested function as its action.
Here's my code:
func createAddView() {
let addButton = UIButton()
func remove(sender: UIButton) {
print("Remove")
}
addButton.addTarget(self, action: #selector(remove(sender:)), for: .touchUpInside)
}
it is giving me this warning:
warning: No method declared with Objective-C selector 'remove'.
I need the ´remove´ function to be nested in the ´createAddView´function because I need to remove and fade out some other UIViews that is being create in the ´createAddView´ function
Anyone knows how I can do this?
You can't do this, cause func remove is exist only in func createAddView block.
There is no restriction to add one #selector() to multiple UIControl's. So you can declare func remove in class block and add it as #selector every time you create a new button.
Because the func remove is create within createAddView() method .
here´s the fixed code :
func createAddView() {
let view = UIView() //added for testing purposes
view.frame = CGRect(x: 0, y: 0, width: 320 , height: 640) //added for testing purposes
let addButton = UIButton()
addButton.frame = CGRect(x: 0, y: 0, width: 100, height: 50) //added for testing purposes
view.addSubview(addButton)
addButton.addTarget(self, action: #selector(remove(sender:)), for: .touchUpInside)
}
func remove(sender: UIButton) {
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.allowUserInteraction, animations: { () -> Void in
print("Add")
}) {(Bool) -> Void in
print("Done")
}
}