This is my first time using Swift and creating an iOS app and I am having trouble retrieving data from a REST API. I am familiar with Android Development but not iOS.
I am trying to use the API from www.thecocktaildb.com.
An example of a request is http://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita.
I would like to use this request and input a string margarita, or any other drink name, from a search bar and then display the array of drinks into a tableview.
Right now when I run, I am not getting any response from the console.
Am I on the right track?
I am also not sure how to display each result (drink) in a table view cell.
Here is my file:
SearchViewController.swift
class SearchViewController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var TableView: UITableView!
#IBOutlet weak var SearchBar: UISearchBar!
// search in progress or not
var isSearching : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
for subView in self.SearchBar.subviews
{
for subsubView in subView.subviews
{
if let textField = subsubView as? UITextField
{
textField.attributedPlaceholder = NSAttributedString(string: NSLocalizedString("Search", comment: ""))
}
}
}
// set search bar delegate
self.SearchBar.delegate = self
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if self.SearchBar.text!.isEmpty {
// set searching false
self.isSearching = false
}else{
// set searghing true
self.isSearching = true
let postEndpoint: String = "http://www.thecocktaildb.com/api/json/v1/1/search.php?s=" + self.SearchBar.text!.lowercaseString
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET on www.thecocktaildb.com")
print(error)
return
}
// parse the result as JSON, since that's what the API provides
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
return
}
if let strDrink = post["strDrink"] as? String {
print("The drink is: " + strDrink)
}
})
task.resume()
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
// hide kwyboard when search button clicked
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
self.SearchBar.resignFirstResponder()
}
// hide keyboard when cancel button clicked
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
self.SearchBar.text = ""
self.SearchBar.resignFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Analizyng the json received from GET request with the provided URL http://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita
{
"drinks":[{ ... }]
}
There is a drinks key, so you should navigate to it before trying to access the deeper levels of the json. Also note that the drinks value is an array of JSON and should be cast to [NSDictionary]
The code below should help you get started with it.
if let drinks = post["drinks"] as? [NSDictionary] {
for drink in drinks {
if let strDrink = drink["strDrink"] as? String {
print("The drink is: " + strDrink)
}
}
}
Related
I am fetching data from an API and works fine when i type in a city in console. The problem i am facing now is with UISearchController and tableView in the code below i want to populate my searched city in the tableView. Now it does not show up anything when i run the app, except in my console that logs my request when a searched in the searchbar
LOG:
Search text: London
Creating request..
Task started
City name: London
Success! JSON decoded
this means using the searchfunction with the APIrequest works, except that i cant see it in my tableView
here is my viewController
import UIKit
class ViewController: UIViewController{
#IBOutlet weak var tblView: UITableView!
let mWeather = WeatherAPI()
var weatherArray = [WeatherStruct]()
var filteredWeatherArray : [String] = []
// dummy data
let originalArray = ["gothenburg", "london", "stockholm", "new york", "washington DC", "thailand", "china", "spain", "paris"]
var searching: Bool {
if weatherArray.count > 0 {
return true
} else {
return false
}
}
let searchController: UISearchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "type in your city here"
navigationItem.searchController = searchController
tblView.delegate = self
tblView.dataSource = self
}
}
// MARK: - extensions
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchText = searchController.searchBar.text ?? "can not search"
print("Search text: \(searchText)")
mWeather.fetchCurrentWeather(city: searchText) { (WeatherStruct) in}
tblView.reloadData()
}
}
// MARK: - Table view data source
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return weatherArray.count // when something is typed on searchbar
}else{
return filteredWeatherArray.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "searchCell", for: indexPath)
if searching {
cell.textLabel?.text = weatherArray[indexPath.row].name
tblView.reloadData()
} else {
cell.textLabel?.text = filteredWeatherArray[indexPath.row]
tblView.reloadData()
}
return cell
}
}
EDIT: as this maybe can help someone to help me = API request handler
func fetchCurrentWeather(city: String, completionHandler: #escaping (WeatherStruct) -> Void) {
// url
let wholeUrl = baseUrlForCurrentWeather + city + appid + metric
let urlString = (wholeUrl)
guard let url: URL = URL(string: urlString) else { return }
// Request
print("Creating request..")
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let unwrappedError = error {
print("Nått gick fel. Error: \(unwrappedError)")
return
}
if let unwrappedData = data {
do {
let decoder = JSONDecoder()
let wdata: WeatherStruct = try decoder.decode(WeatherStruct.self, from: unwrappedData)
print("City name: \(String(describing: wdata.name))")
print("Success! JSON decoded")
completionHandler(wdata)
} catch {
print("Couldnt parse JSON..")
print(error)
}
}
}
// Starta task
task.resume()
print("Task started")
}
You need to update the filteredWeatherArray from the result you are getting from fetchCurrentWeather in your updateSearchResults(for searchController method, here's how:
mWeather.fetchCurrentWeather(city: searchText) { weather in
self.weatherArray = [weather]
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Edit: Even though the above code might work for you, you might not get the desired result. You are trying to display a list from the array of weatherArray, but when you make a call to fetchCurrentWeather you are getting just one result. I have modified my code to set the one array element to set as the weatherArray and reload.
I'm practicing on a sample application that has a social feed page. I'm trying to display each tweet with the corresponding media. I was able to get the text and media but not as one tweet and the further I could get is displaying the media link. Any help on how to get the tweet with the media displayed would be appreciated. To make it clearer the user should be able to view the text and any picture/video from the application without the need to open any links.
import UIKit
class ViewController: UIViewController,
UITableViewDelegate,UITableViewDataSource {
//importing objects
#IBOutlet weak var mytextfield: UITextField!
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var myimageView: UIImageView!
#IBOutlet weak var myTableview: UITableView!
#IBOutlet weak var myScroll: UIScrollView!
var tweets:[String] = []
//Activity Indicator
var activityInd = UIActivityIndicatorView()
func startA()
{
UIApplication.shared.beginIgnoringInteractionEvents()
activityInd.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
activityInd.center = view.center
activityInd.startAnimating()
view.addSubview(activityInd)
}
//setting table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tweets.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
cell.mytextview.text = tweets[indexPath.row]
return cell
}
#IBAction func mysearchbutton(_ sender: UIButton) {
if mytextfield.text != ""
{
startA()
let user = mytextfield.text?.replacingOccurrences(of: " ", with: "")
getStuff(user: user!)
}
}
//Create a function that gets all the stuff
func getStuff(user:String)
{
let url = URL(string: "https://twitter.com/" + user)
let task = URLSession.shared.dataTask(with: url!) { (data,response, error) in
if error != nil
{
DispatchQueue.main.async
{
if let errorMessage = error?.localizedDescription
{
self.myLabel.text = errorMessage
}else{
self.myLabel.text = "There has been an error try again"
}
}
}else{
let webContent:String = String(data: data!,encoding: String.Encoding.utf8)!
if webContent.contains("<title>") && webContent.contains("data-resolved-url-large=\"")
{
//get user name
var array:[String] = webContent.components(separatedBy: "<title>")
array = array[1].components(separatedBy: " |")
let name = array[0]
array.removeAll()
//getprofile pic
array = webContent.components(separatedBy: "data-resolved-url-large=\"")
array = array[1].components(separatedBy: "\"")
let profilePic = array[0]
print(profilePic)
//get tweets
array = webContent.components(separatedBy: "data-aria-label-part=\"0\">")
//get tweets media
// array = webContent.components(separatedBy: "data-pre-embedded=\"true\" dir=\"ltr\" >")
array.remove(at: 0)
for i in 0...array.count-1
{
let newTweet = array[i].components(separatedBy: "<")
array[i] = newTweet[0]
}
self.tweets = array
DispatchQueue.main.async {
self.myLabel.text = name
self.updateImage(url: profilePic)
self.myTableview.reloadData()
self.activityInd.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
}else{
DispatchQueue.main.async {
self.myLabel.text = "User not found"
self.activityInd.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
}
}
}
task.resume()
}
//Function that gets profile pic data
func updateImage(url:String)
{
let url = URL(string: url)
let task = URLSession.shared.dataTask(with: url!){ (data, response, error) in
DispatchQueue.main.async
{
self.myimageView.image = UIImage(data: data!)
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
myScroll.contentSize.height = 1000
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
#SYou can use TwitterKit SDK in iOS for your App. Twitter SDK is is fully capable to fulfil your needs. Whatever feed functionality you want you just need to configure it in twitter kit.
When showing Tweets you can implement these features for your feed :
The style (dark or light)
Colors (text, links, background)
Action Buttons
The delegate (TWTRTweetViewDelegate) to be notified of user interaction with the Tweet
To Show tweets you can do this :
For showing tweets you have two options :
You can load any public tweets (Attention : For Showing Public Tweets You need Public Tweet IDs)
Swift 4
For e.g
//
// PublicTweets.swift
// TwitterFeedDemo
//
// Created by User on 21/12/17.
// Copyright © 2017 Test Pvt. Ltd. All rights reserved.
//
import UIKit
import TwitterKit
class PublicTweets : UITableViewController {
// setup a 'container' for Tweets
var tweets: [TWTRTweet] = [] {
didSet {
tableView.reloadData()
}
}
var prototypeCell: TWTRTweetTableViewCell?
let tweetTableCellReuseIdentifier = "TweetCell"
var isLoadingTweets = false
override func viewDidLoad() {
super.viewDidLoad()
if let user = Twitter.sharedInstance().sessionStore.session()?.userID {
Twitter.sharedInstance().sessionStore.logOutUserID(user)
}
self.tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
// Create a single prototype cell for height calculations.
self.prototypeCell = TWTRTweetTableViewCell(style: .default, reuseIdentifier: tweetTableCellReuseIdentifier)
// Register the identifier for TWTRTweetTableViewCell.
self.tableView.register(TWTRTweetTableViewCell.self, forCellReuseIdentifier: tweetTableCellReuseIdentifier)
// Setup table data
loadTweets()
}
func loadTweets() {
// Do not trigger another request if one is already in progress.
if self.isLoadingTweets {
return
}
self.isLoadingTweets = true
// set tweetIds to find
let tweetIDs = ["944116014828138496","943585637881352192","943840936135741440"];
// Find the tweets with the tweetIDs
let client = TWTRAPIClient()
client.loadTweets(withIDs: tweetIDs) { (twttrs, error) -> Void in
// If there are tweets do something magical
if ((twttrs) != nil) {
// Loop through tweets and do something
for i in twttrs! {
// Append the Tweet to the Tweets to display in the table view.
self.tweets.append(i as TWTRTweet)
}
} else {
print(error as Any)
}
}
}
}
// MARK
// MARK: UITableViewDataSource UITableViewDelegate
extension PublicTweets {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of Tweets.
return tweets.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve the Tweet cell.
let cell = tableView.dequeueReusableCell(withIdentifier: tweetTableCellReuseIdentifier, for: indexPath) as! TWTRTweetTableViewCell
// Assign the delegate to control events on Tweets.
cell.tweetView.delegate = self
cell.tweetView.showActionButtons = true
// Retrieve the Tweet model from loaded Tweets.
let tweet = tweets[indexPath.row]
// Configure the cell with the Tweet.
cell.configure(with: tweet)
// Return the Tweet cell.
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let tweet = self.tweets[indexPath.row]
self.prototypeCell?.configure(with: tweet)
return TWTRTweetTableViewCell.height(for: tweet, style: TWTRTweetViewStyle.compact, width: self.view.bounds.width , showingActions:true)
}
}
extension PublicTweets : TWTRTweetViewDelegate {
//Handle Following Events As Per Your Needs
func tweetView(_ tweetView: TWTRTweetView, didTap url: URL) {
}
func tweetView(_ tweetView: TWTRTweetView, didTapVideoWith videoURL: URL) {
}
func tweetView(_ tweetView: TWTRTweetView, didTap image: UIImage, with imageURL: URL) {
}
func tweetView(_ tweetView: TWTRTweetView, didTap tweet: TWTRTweet) {
}
func tweetView(_ tweetView: TWTRTweetView, didTapProfileImageFor user: TWTRUser) {
}
func tweetView(_ tweetView: TWTRTweetView, didChange newState: TWTRVideoPlaybackState) {
}
}
You can also show other users tweets by just having their ScreenName or Twitter UserID
For e.g.
//
// SelfTweets.swift
// TwitterFeedDemo
//
// Created by User on 21/12/17.
// Copyright © 2017 Test Pvt. Ltd. All rights reserved.
//
import Foundation
import UIKit
import TwitterKit
class SelfTweets: TWTRTimelineViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let user = Twitter.sharedInstance().sessionStore.session()?.userID {
let client = TWTRAPIClient()
self.dataSource = TWTRUserTimelineDataSource.init(screenName:"li_ios", userID: user, apiClient: client, maxTweetsPerRequest: 10, includeReplies: true, includeRetweets: false)
}
}
}
I use a service in a background thread to fetch a post request. Then I use NSJSONSerialization to turn that into an array. I loop thorough the array to create an array of teams. Then i go back to the main queue and call the completion handler.
Team:
class Team
{
private (set) var id: Int
private (set) var city: String
private (set) var name: String
private (set) var abbreviation: String
init(data: JSONDictionary)
{
id = data["team_id"] as? Int ?? 0
city = data["city"] as? String ?? ""
name = data["team_name"] as? String ?? ""
abbreviation = data["abbreviation"] as? String ?? ""
}
}
Service:
func getTeams(urlString: String, completion: [Team] -> Void)
{
let config = NSURLSessionConfiguration.ephemeralSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = NSURL(string: urlString)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
if error != nil {
print(error!.localizedDescription)
} else {
print(data)
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as? JSONArray {
var teams = [Team]()
for team in json {
let team = Team(data: team as! JSONDictionary)
teams.append(team)
}
let priority = DISPATCH_QUEUE_PRIORITY_HIGH
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(teams)
}
}
}
} catch {
print("error in NSJSONSerialization")
}
}
}
task.resume()
}
I then try to use data to populate a tableView. I also loop through and print out all the team names to the console with success. The problem I am having It populate the tableView but everything is all white. I cant see any txt from my labels until I touch it. While the table cell is selected I can see the contents of the labels which are in black. But if i touch another one only the currently selected label is showing. It seems they should all just show up visible once the data is loaded.
custom cell:
class TeamTableViewCell: UITableViewCell {
var team: Team? {
didSet {
updateCell()
}
}
#IBOutlet weak var title: UILabel!
#IBOutlet weak var abbreviation: UILabel!
func updateCell()
{
title.text = team?.name ?? ""
abbreviation.text = team?.abbreviation ?? ""
}
}
Controller:
var teams = [Team]()
override func viewDidLoad() {
super.viewDidLoad()
title = "Teams"
let service = NBAService()
service.getTeams("https://probasketballapi.com/teams?api_key=\(Constants.API.APIKey)", completion: didLoadTeams )
}
func didLoadTeams(teams: [Team])
{
self.teams = teams
tableView.reloadData()
// This actuall works returns an list of team names to the console.
for team in teams {
print("Team: \(team.name)")
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return teams.count
}
struct Storyboard {
static let TeamCell = "TeamCell"
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(Storyboard.TeamCell, forIndexPath: indexPath) as! TeamTableViewCell
// Configure the cell...
cell.team = self.teams[indexPath.row]
return cell
}
When i print the teams names to the console that prints fine so I know that I have successfully got the data back from the request. And one team at a time is visible when the cell is selected. What am I missing
This is kind of strange:
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(teams)
}
}
I would replace this with:
dispatch_async(dispatch_get_main_queue()) {
completion(teams)
}
I am using swift 2.0 . And i have added search bar to table view. i have run 2 times, its worked well. But now in my code its showing error :
Cannot invoke 'filter' with an argument list of type '(#noescape (Element) throws -> Bool)'
When i try to run also not able to search my table view data ,
Here is my full code:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate
{
var Table:NSArray = []
#IBOutlet weak var searchBar: UISearchBar!
var searchActive : Bool = false
var filtered:[String] = []
#IBOutlet weak var tableView: UITableView! // UITable view declaration
#IBOutlet weak var Resultcount: UILabel! // count label
let cellSpacingHeight: CGFloat = 5 // cell spacing from each cell in table view
var filteredTableData = [String]()
var resultSearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
CallWebService() // call the json method
// nib for custom cell (table view)
let nib = UINib(nibName:"customCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "cell")
searchBar.delegate = self
}
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
searchActive = true;
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
searchActive = false;
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchActive = false;
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchActive = false;
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filtered = Table.filter({ (text) -> Bool in
let tmp: NSString = text as! NSString
let range = tmp.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return range.location != NSNotFound
})
if(filtered.count == 0){
searchActive = false;
} else {
searchActive = true;
}
self.tableView.reloadData()
}
// every time app quit and run, switch will be in off state
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "PremiumUser")
}
func CallWebService()
{
let UrlApi = "url"
let Url = NSURL(string: UrlApi)
let Session = NSURLSession.sharedSession()
let Work = Session.dataTaskWithURL(Url!, completionHandler: { dataTask, response, error -> Void in
if (error != nil)
{
print(error)
}
var datos:NSData = NSData(data: dataTask!)
do {
let JsonWithDatos:AnyObject! = try NSJSONSerialization.JSONObjectWithData(datos, options: NSJSONReadingOptions.MutableContainers) as! NSArray
self.Table = JsonWithDatos as! NSArray
dispatch_async(dispatch_get_main_queue()) {
if (self.Table.count>0)
{
self.Resultcount.text = "\(self.Table.count) Results"
self.tableView.reloadData()
}
}
}catch{
print("Some error occured")
}
})
Work.resume()
}
// number of sections
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
// number of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(searchActive) {
return filtered.count
}
return self.Table.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:customCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! customCell
if(searchActive){
cell.vendorName.text = filtered[indexPath.row]
} else {
cell.vendorName.text = Table[indexPath.row] as! String;
}
let item = self.Table[indexPath.row] as! [String : String]
cell.vendorName.text = item["name"]
cell.vendorAddress.text = item["address"]
return cell
}
}
i am getting error in this method func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { in this line filtered = Table.filter({ (text) -> Bool in
let tmp: NSString = text as! NSString
let range = tmp.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return range.location != NSNotFound
})
Why its work one time , that time also not able to do search in my table view. Now I am suddenly getting this error.
i have implemented search controller in table view
i give you my function that filter the searched data and make an array with matching strings
func filterDestinations(searchString: String){
self.filteredDestinations.removeAll()
if(self.defaultarray.count > 0){
for obj in self.sortedDest{
if(obj.name!.rangeOfString(searchString, options: .CaseInsensitiveSearch, range: nil, locale: nil) != nil){
self.filteredDestinations.append(obj)
}
}
}
}// ends filterDestinations
after that you just reload your table view and in function cellforrowatindex you check that if search controller is active than you give data from your filltered array otherwise use your default array.
also you have to set numberofrows by check search controller is active or not.
if its active then return the filltered array count otherwise return your default array count so your app wont crash .
Just remove the closure parameter and return value type. Also rangeOfString returns nil if the string was not found. And better to cast to Swift String not NSString. You are trying to assign NSArray (basically [AnyObject] to [String]. You have to do some mapping.
filtered = table.filter {
let tmp = $0 as! String
let range = tmp.rangeOfString(searchText, options: .CaseInsensitiveSearch)
return range != nil
}.map { $0 as! String }
Good Afternoon,
Today I am having some issues with parse.
I have created a UISearchController, loaded my users from parse so I can search for individual ones and I have added a following and unfollowing feature.
My Problem is when I search for a specific user and try to follow him: So I search for a specific user say "test" it shows up as it should, but when I click follow and then go back to parse to see if "I" have followed test I can a different result.
It says I have followed for example "tester" which was the first user created. Its seeming to follow the Cell and now the userId...
After that I manged to get the users in alphabetical order, but same problem here except it follows the first user in alphabetical order for example if I have a username that begins with an "A"!
I'm not sure how to fix this issue, so I'm hoping someone here does..I accept and appreciate all kind of tips and answers!
Heres my code:
class yahTableViewController: UITableViewController, UISearchResultsUpdating {
var users: [PFUser] = [PFUser]()
var followingList: [PFUser] = [PFUser]()
var searchResults: Bool = false
var resultSearchController = UISearchController()
var refresher: UIRefreshControl!
#IBOutlet var userTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.hidesNavigationBarDuringPresentation = false
self.navigationItem.titleView = resultSearchController.searchBar
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.definesPresentationContext = true
self.resultSearchController.searchBar.sizeToFit()
self.resultSearchController.searchBar.barStyle = UIBarStyle.Black
self.resultSearchController.searchBar.tintColor = UIColor.whiteColor()
for subview in self.resultSearchController.searchBar.subviews
{for subsubView in subview.subviews
{if let textField = subsubView as? UITextField
{textField.attributedPlaceholder = NSAttributedString(string: NSLocalizedString("Search", comment: ""), attributes: [NSForegroundColorAttributeName: UIColor.whiteColor()])
textField.textColor = UIColor.whiteColor()
}}}
tableView.tableFooterView = UIView()
self.tableView.separatorInset = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
refresher = UIRefreshControl()
refresher.attributedTitle = NSAttributedString(string: "")
refresher.addTarget(self, action: "refresh", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refresher)
}
//Function used to load the users on first view load or when the UI refresh is performed
private func loadUsers(searchString: String){
func refresh() {
let query = PFUser.query()
query!.whereKey("username", containsString: searchString )
self.searchResults = true
query!.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
self.users.removeAll(keepCapacity: false)
self.users += objects as! [PFUser]
self.tableView.reloadData()
} else {
// Log details of the failure
print("search query error: \(error) \(error!.userInfo)")
}
// Now get the following data for the current user
let query = PFQuery(className: "followers")
query.whereKey("follower", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if (error == nil) {
self.followingList.removeAll(keepCapacity: false)
self.followingList += objects as! [PFUser]
self.userTableView.reloadData()
} else
if error != nil {
print("Error getting following: \(error) \(error!.userInfo)")
}
})
}
self.searchResults = false
self.tableView.reloadData()
self.refresher.endRefreshing()
}}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
// Force search if user pushes button
let searchString: String = searchBar.text!.lowercaseString
if (searchString != "") {
loadUsers(searchString)
}
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchBar.text = ""
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchString: String = searchController.searchBar.text!.lowercaseString
if (searchString != "" && !self.searchResults) {
loadUsers(searchString)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.resultSearchController.active) {
return self.users.count
} else {
return self.users.count
// return whatever your normal data source is
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let Cell = tableView.dequeueReusableCellWithIdentifier("Cell")! as UITableViewCell
if (self.resultSearchController.active && self.users.count > indexPath.row) {
let userObject = users[indexPath.row]
Cell.textLabel?.text = userObject.username
for following in followingList {
if following["following"] as? String == PFUser.currentUser()! {
//Add checkbox to cell
Cell.accessoryType = UITableViewCellAccessoryType.Checkmark
break
}}
// bind data to the search results cell
} else {
// bind data from your normal data source
}
return Cell
}
// MARK: - UITableViewDelegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let selectedUser = users[indexPath.row] as? PFUser {
// Now get the following/following data for the current user
let query = PFQuery(className: "Followers")
query.whereKey("follower", equalTo: (PFUser.currentUser()?.objectId)!)
query.whereKey("following", equalTo: (selectedUser.objectId)!)
query.getFirstObjectInBackgroundWithBlock({ (object, error) -> Void in
if error != nil && object == nil {
// Means the record doesn't exist
self.insertFollowingRecord(selectedUser, selectedIndexPath: indexPath)
} else {
// Means record is present, so we will delete it
if let followingObject = object {
followingObject.deleteInBackground()
let cell:UITableViewCell = self.userTableView.cellForRowAtIndexPath(indexPath)!
//Remove checkbox from cell
cell.accessoryType = UITableViewCellAccessoryType.None
}
}
})
}
}
private func insertFollowingRecord (selectedUser:PFUser, selectedIndexPath: NSIndexPath) -> Void {
// Now add the data for following in parse
let following:PFObject = PFObject(className: "Followers")
following["following"] = selectedUser.objectId
following["follower"] = PFUser.currentUser()?.objectId
following.saveInBackgroundWithBlock({ (success, error) -> Void in
if success {
let cell:UITableViewCell = self.userTableView.cellForRowAtIndexPath(selectedIndexPath)!
//Add checkbox to cell
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
} else if error != nil {
print("Error getting following: \(error) \(error!.userInfo)")
}
})
}
}
You will want to implement the UISearchResultsUpdating protocol to achieve this. It uses a UISearchController (introduced in iOS 8) which has to be added programmatically instead of through the storyboard, but don't worry, it's pretty straight-forward.
This should get the job done for you
Courtesy of Russel.
class YourTableViewController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
var searchUsers: [PFUser] = [PFUser]()
var userSearchController = UISearchController()
var searchActive: Bool = false
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.userSearchController = UISearchController(searchResultsController: nil)
self.userSearchController.dimsBackgroundDuringPresentation = true
// This is used for dynamic search results updating while the user types
// Requires UISearchResultsUpdating delegate
self.userSearchController.searchResultsUpdater = self
// Configure the search controller's search bar
self.userSearchController.searchBar.placeholder = "Search for a user"
self.userSearchController.searchBar.sizeToFit()
self.userSearchController.searchBar.delegate = self
self.definesPresentationContext = true
// Set the search controller to the header of the table
self.tableView.tableHeaderView = self.userSearchController.searchBar
}
// MARK: - Parse Backend methods
func loadSearchUsers(searchString: String) {
var query = PFUser.query()
// Filter by search string
query.whereKey("username", containsString: searchString)
self.searchActive = true
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
if (error == nil) {
self.searchUsers.removeAll(keepCapacity: false)
self.searchUsers += objects as! [PFUser]
self.tableView.reloadData()
} else {
// Log details of the failure
println("search query error: \(error) \(error!.userInfo!)")
}
self.searchActive = false
}
}
// MARK: - Search Bar Delegate Methods
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
// Force search if user pushes button
let searchString: String = searchBar.text.lowercaseString
if (searchString != "") {
loadSearchUsers(searchString)
}
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
// Clear any search criteria
searchBar.text = ""
// Force reload of table data from normal data source
}
// MARK: - UISearchResultsUpdating Methods
// This function is used along with UISearchResultsUpdating for dynamic search results processing
// Called anytime the search bar text is changed
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchString: String = searchController.searchBar.text.lowercaseString
if (searchString != "" && !self.searchActive) {
loadSearchUsers(searchString)
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.userSearchController.active) {
return self.searchUsers.count
} else {
// return whatever your normal data source is
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("userCell") as! UserCell
if (self.userSearchController.active && self.searchUsers.count > indexPath.row) {
// bind data to the search results cell
} else {
// bind data from your normal data source
}
return cell
}
// MARK: - UITableViewDelegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if (self.userSearchController.active && self.searchUsers.count > 0) {
// Segue or whatever you want
} else {
// normal data source selection
}
}
}