TableView result error - tableview

I have a Search Bar with a UITableView and if I search something in the searchBar it will print the result in the table view.
If I search a name like "Name 01" and I click on this name to get information and later I re-open the Search Bar and I try to search other name like "Name 02" I will see the "Name 01" result in the Table View and I don't know how to clear it.
I have tried to refresh Table View too but without success.
Video of the problem: https://streamable.com/98j0w
The code is this
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
//print("updateSearchResults")
if searchController.searchBar.text == nil {
seenNames.removeAll()
matchingItems.removeAll()
self.tableView.reloadData()
}
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { response, _ in
guard let response = response else {
return
}
for (index , name) in response.mapItems.enumerated() {
let item = response.mapItems[index]
if(checkIfItemExistInDatabase(key: String(item.name!)) != nil && !seenNames.contains(name.name!)){
matchingItems.append(item)
seenNames.insert(name.name!)
self.tableView.reloadData()
}
}
}
}
}
I want that If I do a research the tableview result with searchbar text is cleaned and doesn’t show the previously result

Instead of:
if searchController.searchBar.text == nil {
...
...
Try with
let searchText = searchController.searchBar.text
if searchText == nil || searchText.isEmpty {
...
...
And another thing, just before
search.start { response, _ in
add
matchingItems.removeAll()
tableView.reloadData()

Related

How Does One Display an Web-based Image in SwiftUI

SwiftUI seems cool, but some things just seem hard to me. Even so, I would rather understand how best to do something the SwiftUI way rather than wrap pre-swiftui controllers and do something the old way. So let me start with a simple problem -- displaying a web image given a URL. There are solutions, but they are not all that easy to find and not all the easy to understand.
I have a solution and would like some feedback. Below is an example of what I would like to do (the images is from Open Images).
struct ContentView: View {
#State var imagePath: String = "https://farm2.staticflickr.com/440/19711210125_6c12414d8f_o.jpg"
var body: some View {
WebImage(imagePath: $imagePath).scaledToFit()
}
}
My solution entails putting a little bit of code at the top of the body to start the image download. The image path has a #Binding property wrapper -- if it changes I want to update my view. There is also a myimage variable with a #State property wrapper -- when it gets set I also want to update my view. If everything goes well with the image load, myimage will be set and the an image displays. The initial problem is that changing the state within the body will result in the view being invalidated and trigger yet another download, ad infinitum. The solution seems simple (the code is below). Just check imagePath and see if it has changed since the last time something was loaded. Note that in download I set prev immediately, which triggers another execution of body. The conditional causes the state change to be ignored.
I read somewhere that #State checks for equality and will ignore sets if the value does not change. This kind of equality check will fail for UIImage. I expect three invocations of body: the initial invocation, the invocation when I set prev, and an invocation when I set image. I suppose I could add a mutable value for prev (i.e., a simple class) and avoid the second invocation.
Note that loading web content could have been accomplished using an extension and closures, but that's a different issue. Doing so, would have shrunk WebImage to just a few lines of code.
So, is there a better way to accomplish this task?
//
// ContentView.swift
// Learn
//
// Created by John Morris on 11/26/19.
// Copyright © 2019 John Morris. All rights reserved.
//
import SwiftUI
struct WebImage: View {
#Binding var imagePath: String?
#State var prev: String?
#State var myimage: UIImage?
#State var message: String?
var body: some View {
if imagePath != prev {
self.downloadImage(from: imagePath)
}
return VStack {
myimage.map({Image(uiImage: $0).resizable()})
message.map({Text("\($0)")})
}
}
init?(imagePath: Binding<String?>) {
guard imagePath.wrappedValue != nil else {
return nil
}
self._imagePath = imagePath
guard let _ = URL(string: self.imagePath!) else {
return nil
}
}
func getData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
func downloadImage(from imagePath: String?) {
DispatchQueue.main.async() {
self.prev = imagePath
}
guard let imagePath = imagePath, let url = URL(string: imagePath) else {
self.message = "Image path is not URL"
return
}
getData(from: url) { data, response, error in
if let error = error {
self.message = error.localizedDescription
return
}
guard let httpResponse = response as? HTTPURLResponse else {
self.message = "No Response"
return
}
guard (200...299).contains(httpResponse.statusCode) else {
if httpResponse.statusCode == 404 {
self.message = "Page, \(url.absoluteURL), not found"
} else {
self.message = "HTTP Status Code \(httpResponse.statusCode)"
}
return
}
guard let mimeType = httpResponse.mimeType else {
self.message = "No mimetype"
return
}
guard mimeType == "image/jpeg" else {
self.message = "Wrong mimetype"
return
}
print(response.debugDescription)
guard let data = data else {
self.message = "No Data"
return
}
if let image = UIImage(data: data) {
DispatchQueue.main.async() {
self.myimage = image
}
}
}
}
}
struct ContentView: View {
var images = ["https://c1.staticflickr.com/3/2260/5744476392_5d025d6a6a_o.jpg",
"https://c1.staticflickr.com/9/8521/8685165984_e0fcc1dc07_o.jpg",
"https://farm1.staticflickr.com/204/507064030_0d0cbc850c_o.jpg",
"https://farm2.staticflickr.com/440/19711210125_6c12414d8f_o.jpg"
]
#State var imageURL: String?
#State var count = 0
var body: some View {
VStack {
WebImage(imagePath: $imageURL).scaledToFit()
Button(action: {
self.imageURL = self.images[self.count]
self.count += 1
if self.count >= self.images.count {
self.count = 0
}
}) {
Text("Next")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I would suggest two things. First, you generally want to allow a placeholder View for when the image is downloading. Second, you should cache the image otherwise if you have something like a tableView where it scrolls off screen and back on screen, you are going to keep downloading the image over an over again. Here is an example from one of my apps of how I addressed it:
import SwiftUI
import Combine
import UIKit
class ImageCache {
enum Error: Swift.Error {
case dataConversionFailed
case sessionError(Swift.Error)
}
static let shared = ImageCache()
private let cache = NSCache<NSURL, UIImage>()
private init() { }
static func image(for url: URL) -> AnyPublisher<UIImage?, ImageCache.Error> {
guard let image = shared.cache.object(forKey: url as NSURL) else {
return URLSession
.shared
.dataTaskPublisher(for: url)
.tryMap { (tuple) -> UIImage in
let (data, _) = tuple
guard let image = UIImage(data: data) else {
throw Error.dataConversionFailed
}
shared.cache.setObject(image, forKey: url as NSURL)
return image
}
.mapError({ error in Error.sessionError(error) })
.eraseToAnyPublisher()
}
return Just(image)
.mapError({ _ in fatalError() })
.eraseToAnyPublisher()
}
}
class ImageModel: ObservableObject {
#Published var image: UIImage? = nil
var cacheSubscription: AnyCancellable?
init(url: URL) {
cacheSubscription = ImageCache
.image(for: url)
.replaceError(with: nil)
.receive(on: RunLoop.main, options: .none)
.assign(to: \.image, on: self)
}
}
struct RemoteImage : View {
#ObservedObject var imageModel: ImageModel
init(url: URL) {
imageModel = ImageModel(url: url)
}
var body: some View {
imageModel
.image
.map { Image(uiImage:$0).resizable() }
?? Image(systemName: "questionmark").resizable()
}
}

How do I filter data from database arrays using Search bar

Im using the search bar to filter data from the database and will populate the tableView. Im getting an error on the isSearching part.
Value of type 'DataSnapshot' has no member 'contains'
Heres the code.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
isSearching = false
view.endEditing(true)
tableView.reloadData()
} else {
isSearching = true
filteredColorRequests = colors.filter{$0.contains(searchBar.text!)}
tableView.reloadData()
}
}
How
You certainly want to search for a specific String property for example a name.
And rather than getting the search string form the bar use the searchText parameter which is already non-optional.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
isSearching = false
view.endEditing(true)
} else {
isSearching = true
filteredColorRequests = colors.filter{(($0.value as! [String:Any])["name"] as! String).contains(searchText)}
}
tableView.reloadData()
}

Delete on swipe, removes first item in the list when you swipe any one

Every time I swipe item in table view and delete it, it deletes the first item in the list, not the one I swipe. I tried different approaches but still doing the same
Here is my view controller function, where I swipe and call delete method.
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let modifyAction = UIContextualAction(style: .normal, title: "Modify", handler:
{ ac, view, success in
var stmt: OpaquePointer?
let deleteStatementStirng = "SELECT * FROM ShoppingList"
if sqlite3_prepare(self.db, deleteStatementStirng, -1, &stmt, nil) != SQLITE_OK
{
print("error preparing delete")
}
if sqlite3_step(stmt) == SQLITE_ROW {
let id = sqlite3_column_int(stmt, 0)
self.deleteRow(itemId: Int32(id))
}
success(true)
}
)
modifyAction.backgroundColor = .red
return UISwipeActionsConfiguration(actions: [modifyAction])
}
And also my delete function:
func deleteRow(itemId: Int32){
let deleteStatementStirng = "DELETE FROM ShoppingList WHERE id = \(itemId)"
var deleteStatement: OpaquePointer?
if sqlite3_prepare(db, deleteStatementStirng, -1, &deleteStatement, nil) == SQLITE_OK{
if sqlite3_step(deleteStatement) == SQLITE_DONE {
print("Successfully deleted row.")
} else {
print("Could not delete row.")
}
} else {
print("DELETE statement could not be prepared")
}
sqlite3_finalize(deleteStatement)
readValues()
}
Expected result is to delete item from the list
Just update your modifyAction code with: (Added indexPath.row instead 0 while getting id)
let modifyAction = UIContextualAction(style: .normal, title: "Modify", handler:
{ ac, view, success in
var stmt: OpaquePointer?
let deleteStatementStirng = "SELECT * FROM ShoppingList"
if sqlite3_prepare(self.db, deleteStatementStirng, -1, &stmt, nil) != SQLITE_OK
{
print("error preparing delete")
}
if sqlite3_step(stmt) == SQLITE_ROW {
let id = sqlite3_column_int(stmt, indexPath.row)
self.deleteRow(itemId: Int32(id))
}
success(true)
}
)
You should use
indexPath.row
to get actual index of row that you want to perform action on.

custom button with image for FBSDKLoginButton

i want use a custom button with image ,
I don't want to use Facebook default login button.
this is my code
let loginButton = FBSDKLoginButton()
loginButton.readPermissions = ["public_profile", "email", "user_friends"]
loginButton.center = self.view.center
loginButton.delegate = self
loginButton.imageView?.image = UIImage(named: "cyanx2")
self.view.addSubview(loginButton)
and my button would be
#IBAction func customButton(sender: UIButton) {
// here's go the action when click on default facebook button
}
According to this post
i fix my problem ^_^ and this is the code
button function
#IBAction func loginFacebookAction(sender: AnyObject) {
let fbLoginManager : FBSDKLoginManager = FBSDKLoginManager()
fbLoginManager.logInWithReadPermissions(["email"], fromViewController: self) { (result, error) -> Void in
if error != nil {
NSLog("Process error")
}
else if result.isCancelled {
NSLog("Cancelled")
}
else {
NSLog("Logged in")
self.getFBUserData()
}
}
}
function to fetch the result from facebook
func getFBUserData(){
if((FBSDKAccessToken.currentAccessToken()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, first_name, last_name, picture.type(large), email"]).startWithCompletionHandler({ (connection, result, error) -> Void in
if (error == nil){
//everything works print the user data
print(result)
}
})
}
}

Continuously update a UILabel in Swift

I have a function, shown below, that I would like to continuously update. It is taking data from a webpage, and every so often that webpage is updated to reflect current information. Is there a way that I can catch this update and reflect that in my application? I'm pretty new to Swift and iOS programming. Some of the code made seem very bizarre, but it currently works for whatever song is playing when you first open the app (that is, it updates the text to show that song playing but doesn't update later).
let url = NSURL(string: "http://api.vicradio.org/songs/current")!
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) in
if error != nil {
return
}
let name = NSString(data: data!, encoding: NSUTF8StringEncoding) as! String
var songName = ""
var artistName = "by "
var quoteNumber = 0
for character in name.characters {
if character == "\"" {
quoteNumber++
}
if quoteNumber == 3 && character != "\"" {
songName += String(character)
} else if quoteNumber == 7 && character != "\"" {
artistName += String(character)
}
}
if (songName != "no song metadata provided") {
self.SongNowText.text = songName
self.ArtistNowText.text = artistName
self.SongNowText.setNeedsDisplay()
self.ArtistNowText.setNeedsDisplay()
} else if (songName == "no song metadata provided") {
self.SongNowText.text = "The Best of What's Next!"
self.ArtistNowText.text = "only on VIC Radio"
}
}
task!.resume()
It looks like the URL you're accessing there is an API endpoint putting out JSON. I highly recommend using NSJSONSerialization.JSONObjectWithData to parse the response body into a dictionary and use that instead of rolling your own solution by counting quote marks.
The callback to dataTaskWithURL is executed on a background thread. Avoid updating the UI on anything besides the main thread because it can cause problems. Use dispatch_async to execute your UI update function on the main thread as in the example.
All you can do with this API is send it requests and read the responses. You can poll the endpoint at a regular interval while the app is open and get decent results from that. NSTimer is one way to do that, and it requires you put the method you want to execute repeatedly in a class inheriting from NSObject because it depends on Objective-C style message sending.
Throw this in a playground and try it:
import Cocoa
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely()
class RadioDataAccessor : NSObject {
private let callback: [String : AnyObject] -> Void
init(callback: [String : AnyObject] -> Void) {
self.callback = callback
super.init()
NSTimer.scheduledTimerWithTimeInterval(5.0, target: self,
selector: "updateData", userInfo: nil, repeats: true)
// just so it happens quickly the first time
updateData()
}
func updateData() {
let session = NSURLSession.sharedSession()
let url = NSURL(string: "http://api.vicradio.org/songs/current")!
session.dataTaskWithURL(url) { data, response, error in
if error != nil {
return
}
var jsonError = NSErrorPointer()
let json = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.allZeros,
error: jsonError) as? [String : AnyObject]
if jsonError != nil {
return
}
dispatch_async(dispatch_get_main_queue()) { self.callback(json!) }
}.resume()
}
}
RadioDataAccessor() { data in
println(data)
}
You may want to save the timer to a variable and expose a function that lets you invalidate it.

Resources