swift+tableview data is not populated when searchbar is active - json

When I load my table then activate the search, I can get the search data in my tableview. To make my app faster, I create a seperate view only for search. But I couldn't get my tableview populated with search data. I can see I get the data correctly with print(data), but my table is not populated.
class YazarTableViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {
var searchResults: [JSON]? = []
var searchController = UISearchController()
#IBOutlet weak var searchButton: UIBarButtonItem!
#IBAction func searchButtonTouched(sender: AnyObject) {
showSearchBar()
}
func showSearchBar() {
self.searchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.searchBar.delegate = self
controller.dimsBackgroundDuringPresentation = true
controller.searchBar.alpha = 0
navigationItem.setLeftBarButtonItem(nil, animated: true)
UIView.animateWithDuration(0.5, animations: {
controller.searchBar.alpha = 1
}, completion: { finished in
controller.searchBar.becomeFirstResponder()
})
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
}
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
self.searchResults?.removeAll(keepCapacity: false)
if (!searchController.searchBar.text!.isEmpty){
let parameters = ["q" : searchController.searchBar.text]
var searchURL = "http://apilink.com/" + String(parameters) + "/1"
searchURL = searchURL.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
Alamofire.request(.GET, searchURL, encoding: .JSON)
.responseJSON{ response in
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling GET on /posts/1")
print(response.result.error!)
return
}
if let value: AnyObject = response.result.value {
let post = JSON(value)
if let data = post[].arrayValue as [JSON]?{
self.searchResults = data
self.tableView.reloadData()
print(data)
}
}
}
}
}

Related

How to fetch JSON data in background Swiftui

I have a JSON API I want my app to download in background.
My Quotes app send out notifications with timeintervals and I want the quotes to be in the notification.
struct Quotetype: Codable {
let text: String?
let author: String?
}
class ViewModel: ObservableObject {
#Published var quotes: [Quotetype]?
#Published var isLoading: Bool = true
var quoteText: String = ""
var quoteAuthor: String = ""
init() {
fetchQuotes()
setUpNotificationPermission()
setUpNotificationTriggers()
}
func fetchQuotes() {
guard let url = URL(string: "https://type.fit/api/quotes") else { return }
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
if error == nil {
if let data = data {
do {
let jsonContent = try JSONDecoder().decode([Quotetype].self, from: data)
DispatchQueue.main.async { [self] in
self?.quotes = jsonContent
if self?.quotes?.count != nil {
let randomNumber = Int.random(in: 0..<self!.quotes!.count )
self?.isLoading = false
self?.quoteText = self!.quotes?[randomNumber].text ?? ""
self?.quoteAuthor = self!.quotes?[randomNumber].author ?? ""
}
}
} catch {
print("\(error)")
}
} else {
print("Data nil")
}
} else {
print("err \(String(describing: error))")
}
}.resume()
}
func setUpNotificationTriggers() {
let content = UNMutableNotificationContent()
content.title = "Quote of the Day"
content.subtitle = quoteText
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
this is my fetch & Notification. How can I get the quoteText in background so notification can go through?
please provide with examples.

Swift not recognising new elements from JSON in LazyHStack

Hey guys so I have a problem with decoding JSON. I decoded the entire string and made a statement in the ViewModel which fetches only elements as such
#Published var posts = [Post]()
var postsid6: [Post] {
var typeid6posts = [Post]()
for post in self.posts { if post.type_id == "6" { typeid6posts.append(post) }
}
return typeid6posts
}
So if it's type_id="6" it fetches them seperetly and I call the function like this
LazyHStack(alignment: .center){
ForEach(viewModel.postsid6) { post in
NavigationLink(destination: StoryDetailsView(post)) {
SingleStoryView(post: post, dateFormatter: viewModel.dateFormatter)
.padding(.bottom, 16)
}
}
}
Problem is for some reason swift fetches only 2 of them and there are 7 in the JSON with type_id="6"
This is the entire ViewModel. Posts are being updated but the typeid6posts isn't adding new ones
//
// PostsViewModel.swift
// EMDC App (iOS)
//
// Created by admin on 19.6.21..
//
import Foundation
class PostsViewModel: ObservableObject{
lazy var dateFormatter: DateFormatter = {
let df = DateFormatter()
df.doesRelativeDateFormatting = true
df.timeStyle = .short
df.dateStyle = .short
return df
}()
struct PostsResponse: Codable{
let items: [Post]
let users: [String : UserModel]
}
#Published var posts = [Post]()
var postsid6: [Post] {
var typeid6posts = [Post]()
for post in self.posts { if post.type_id == "6" { typeid6posts.append(post) }
}
return typeid6posts
}
var videibgt: [Post] {
var videiisat = [Post]()
for post in self.posts { if (post.videourl != nil) == true { videiisat.append(post) } }
return videiisat
}
private let jsonDecoder = JSONDecoder()
func fetchPosts(){
let url = URL(string: "https://cdn.rapttor.com/influencer/data/2/posts.json")!
URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do{
let decoded = try self.jsonDecoder.decode(PostsResponse.self, from: data)
DispatchQueue.main.async {
let posts = decoded.items
var postsWithUser = [Post]()
for var post in posts{
post.user = decoded.users[post.userId]!
postsWithUser.append(post)
}
self.posts.append(contentsOf: postsWithUser)
print(self.posts.count)
}
}
catch{
print(error)
}
}.resume()
}
func duplicate(){
posts.append(contentsOf: posts)
}
}

How can I keep the value of a variable outside a function?

I'm trying to display the value I get from a JSON in an AR text, within the DecodeJSON function it all works, I even achieve to put that value in a normal label but when I try to set that value to the AR text it is empty... what can I do or what I'm doing wrong? It would be very useful if you can help me out with this.
#IBOutlet var sceneView: ARSCNView!
#IBOutlet weak var labelTest: UILabel!
let URL_VWC = "http://w1.doomdns.com:11000/restaguapotable/api/celula/10/sitio/4";
var name :String!
struct JSONTest: Codable {
let Nombre: String
let Tiempo: String
}
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene()
// Set the scene to the view
sceneView.scene = scene
DecodeJson();
let text = SCNText(string: name, extrusionDepth: 1.0)
text.firstMaterial?.diffuse.contents = UIColor.black
let textNode = SCNNode(geometry: text)
textNode.position = SCNVector3(0,0, -0.5)
textNode.scale = SCNVector3(0.02,0.02,0.02)
sceneView.scene.rootNode.addChildNode(textNode)
}
func DecodeJson(){
guard let url = URL(string: URL_VWC) else { return }
// 2
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
do {
// 3
//Decode data
let JSONData = try JSONDecoder().decode(JSONTest.self, from: data)
// 4
//Get back to the main queue
DispatchQueue.main.async {
self.name = JSONData.Nombre
self.labelTest.text = self.name
}
} catch let jsonError {
print(jsonError)
}
// 5
}.resume()
}
This means that DecodeJson() returns self.name = JSONData.Nombre after let text = SCNText(string: name, extrusionDepth: 1.0) is called.
You should have a separate function where you setup your scene after the DecodeJson() has returned the JSON

how to parse complex JSON and show it inside and outside of CollectionView

I have been dealing with JSON for a month now , getting better day by day. Here is a complex jigsaw I have been circling around. JSON return I get is like this :
{
"main_content": [
{
"product_title": "product 3",
"description": "",
"regular_price": "320",
"sale_price": "",
"product_currency": "$",
"size": [
{
"size_data": "L"
},
{
"size_data": "S"
}
],
"color": [
{
"color_data": "RED"
},
{
"color_data": "WHITE"
}
],
"gallery": [
{
"guid": "http://xxx/wp-content/uploads/2016/11/catagory1.jpg"
},
{
"guid": "http://xxx/wp-content/uploads/2016/11/catagory3.jpg"
}
]
}
]
}
Now , here product_title , description , regular_price , sale_price and product_currency will be outside of array as you can see. For size & color I need to fetch as array outside of my CollectionView , I dunno how to iterate through the array outside of a collectionView or tableView, coz in those I got an indexpath to iterate but I dunno what to do outside a collectionView or tableView. Finally My Image slider will be inside CollectionView therefore gallery items needs to be inside that. Here is visual image of my respective page.
Now here is the POST call using Alamofire in my view
import Foundation
import Alamofire
import SwiftyJSON
//Error could happen for these reason
enum ProductDetailsManagerError: Error {
case network(error: Error)
case apiProvidedError(reason: String)
case authCouldNot(reason: String)
case authLost(reason: String)
case objectSerialization(reason: String)
}
// APIManager Class
class ProductDetailsManager {
// THE RIGHT WAY A.K.A. "THE ONE LINE SINGLETON (NOW WITH PROOF!")
static let sharedInstance = ProductDetailsManager()
func printPublicGists(parameter: [String:Any]? , completionHandler: #escaping (Result<[ProductDetailsJSON]>) -> Void) {
let url: String = "http://xxx/wp-api/products/get_product_informations/"
Alamofire.request(url, method: .post, parameters: parameter, encoding: URLEncoding.default, headers: nil)
.responseJSON { (response) in
guard response.result.error == nil else {
print(response.result.error!)
return
}
guard let value = response.result.value else {
print("no string received in response when swapping oauth code for token")
return
}
print(value)
}
}
func fetchPublicGists(parameter: [String:Any]? , completionHandler: #escaping (Result<[ProductDetailsJSON]>) -> Void) {
let url: String = "http://xxx/wp-api/products/get_product_informations/"
Alamofire.request(url, method: .post, parameters: parameter, encoding: URLEncoding.default, headers: nil)
.responseJSON { response in
let result = self.gistArrayFromResponse(response: response)
completionHandler(result)
}
}
// Download Image from URL
func imageFrom(urlString: String, completionHandler: #escaping (UIImage?, Error?) -> Void) {
let _ = Alamofire.request(urlString)
.response { dataResponse in
// use the generic response serializer that returns Data
guard let data = dataResponse.data else {
completionHandler(nil, dataResponse.error)
return
}
let image = UIImage(data: data)
completionHandler(image, nil)
}
}
//gistArrayFromResponse function
private func gistArrayFromResponse(response: DataResponse<Any>) -> Result<[ProductDetailsJSON]> {
// For Network Error
guard response.result.error == nil else {
print(response.result.error!)
return .failure(RueDu8APIManagerError.network(error: response.result.error!))
}
// JSON Serialization Error, make sure we got JSON and it's an array
guard let jsonArray = response.result.value else {
print("did not get array of homeFeed object as JSON from API")
return .failure(RueDu8APIManagerError.objectSerialization(reason: "Did not get JSON dictionary in response"))
}
//turn JSON into gists
//let gistss = jsonArray.flatMap { HomeFeedJSON(items: $0) }
var gists = [ProductDetailsJSON]()
let jsonR = JSON(jsonArray)
let main_content = jsonR["main_content"].array
for item in main_content! {
gists.append(ProductDetailsJSON(items: item))
}
return .success(gists)
}//gistArrayFromResponse() function ends here
}
here is my model class
import Foundation
import SwiftyJSON
class ProductDetailsJSON {
var _product_title: String?
var _description: String?
var _regular_price: String?
var _sale_price: String?
var _product_currency: String?
var _size: String?
var _color: String?
var _image: URL?
init(items: JSON){
self._product_title = items["product_title"].stringValue
self._description = items["description"].stringValue
self._regular_price = items["regular_price"].stringValue
self._sale_price = items["sale_price"].stringValue
self._product_currency = items["product_currency"].stringValue
let sizeData = items["size"].arrayValue
for itemsIMG in sizeData {
self._size = itemsIMG["size_data"].stringValue
}
let colorData = items["color"].arrayValue
for itemsColor in colorData {
self._size = itemsColor["color_data"].stringValue
}
let galleryImg = items["gallery"].arrayValue
for image in galleryImg {
self._image = image["guid"].URL
}
}
var product_title: String {
if _product_title == nil {
_product_title = ""
}
return _product_title!
}
var description: String {
if _description == nil {
_description = ""
}
return _description!
}
var regular_price: String {
if _regular_price == nil {
_regular_price = ""
}
return _regular_price!
}
var sale_price: String {
if _sale_price == nil {
_sale_price = ""
}
return _sale_price!
}
var product_currency: String {
if _product_currency == nil {
_product_currency = ""
}
return _product_currency!
}
var product_color: String {
if _color == nil {
_color = ""
}
return _size!
}
var product_image: URL {
if _image == nil {
let myURL = "http://www.clker.com/cliparts/d/L/P/X/z/i/no-image-icon-hi.png"
let noImage: URL = URL(string: myURL)!
_image = noImage
}
return _image!
}
}
and here is my controller class where I am struggling to show the size , color and gallery items from JSON
import UIKit
import DropDown
import Alamofire
import SwiftyJSON
class ShopItemVC: UIViewController , UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var selectedProductImg: UIImageView!
#IBOutlet weak var backgroundCardView1: UIView!
#IBOutlet weak var backgroundCardView2: UIView!
#IBOutlet weak var backgroundCardView3: UIView!
#IBOutlet weak var sizeBtn: NiceButton!
#IBOutlet weak var colorBtn: NiceButton!
#IBOutlet weak var productPrice: UILabel!
#IBOutlet weak var productTitle: UILabel!
// var Title = [ProductDetailsJSON]()
var product_id:Int? //got value from SpecificCatagoryVC
var product_detail = [ProductDetailsJSON]()
var reloadData = 0
let sizeDropDown = DropDown()
let colorDropDown = DropDown()
lazy var dropDowns: [DropDown] = {
return [
self.sizeDropDown,
self.colorDropDown
]
}()
let CatagoryPic = ["catagory1","catagory2","catagory3","catagory4","catagory5","catagory6","c atagory7"]
// let CatagoryPicture = [ProductDetailsJSON]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
sizeBtn.layer.borderWidth = 1.2
sizeBtn.layer.borderColor = UIColor.black.cgColor
colorBtn.layer.borderWidth = 1.2
colorBtn.layer.borderColor = UIColor.black.cgColor
backgroundCardView1.backgroundColor = UIColor.white
backgroundCardView1.layer.cornerRadius = 5.0
backgroundCardView1.layer.masksToBounds = false
backgroundCardView1.layer.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor
backgroundCardView1.layer.shadowOffset = CGSize(width: 0, height: 0)
backgroundCardView1.layer.shadowOpacity = 0.8
backgroundCardView2.backgroundColor = UIColor.white
backgroundCardView2.layer.cornerRadius = 5.0
backgroundCardView2.layer.masksToBounds = false
backgroundCardView2.layer.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor
backgroundCardView2.layer.shadowOffset = CGSize(width: 0, height: 0)
backgroundCardView2.layer.shadowOpacity = 0.8
backgroundCardView3.backgroundColor = UIColor.white
backgroundCardView3.layer.cornerRadius = 5.0
backgroundCardView3.layer.masksToBounds = false
backgroundCardView3.layer.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor
backgroundCardView3.layer.shadowOffset = CGSize(width: 0, height: 0)
backgroundCardView3.layer.shadowOpacity = 0.8
setupDropDowns()
}
override func viewDidAppear(_ animated: Bool) {
self.scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height + 40)
loadGists(parameter: ["product_id":product_id ?? 0])
}
func setupDropDowns() {
setupSizeDropDown()
setupColorDropDown()
}
func setupSizeDropDown() {
sizeDropDown.anchorView = sizeBtn
sizeDropDown.bottomOffset = CGPoint(x: 0, y: sizeBtn.bounds.height)
// You can also use localizationKeysDataSource instead. Check the docs.
sizeDropDown.dataSource = [
"XXL",
"XL",
"L",
"M",
"S"
]
// Action triggered on selection
sizeDropDown.selectionAction = { [unowned self] (index, item) in
self.sizeBtn.setTitle(item, for: .normal)
print(item)
}
}
func setupColorDropDown() {
colorDropDown.anchorView = colorBtn
colorDropDown.bottomOffset = CGPoint(x: 0, y: colorBtn.bounds.height)
// You can also use localizationKeysDataSource instead. Check the docs.
colorDropDown.dataSource = [
"Red",
"Blue",
"White",
"Purple",
"Pink"
]
// Action triggered on selection
colorDropDown.selectionAction = { [unowned self] (index, item) in
self.colorBtn.setTitle(item, for: .normal)
print(item)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadGists(parameter: [String:Any]?) {
ProductDetailsManager.sharedInstance.fetchPublicGists(parameter: parameter) {
(result) in
guard result.error == nil else {
self.handleLoadGistsError(result.error!)
return
}
if let fetchedGists = result.value {
self.product_detail = fetchedGists
}
self.reloadData = 1
self.collectionView?.reloadData()
}
}
func handleLoadGistsError(_ error: Error) { }
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return CatagoryPic.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ShopItemCell", for: indexPath) as! ShopItemCell
if reloadData == 1 {
let myProduct = self.product_detail[indexPath.row]
self.productTitle.text = myProduct.product_title
}
cell.shopItemPic.image = UIImage(named: CatagoryPic[(indexPath as NSIndexPath).row])
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// here you know which item is selected by accessing indexPath.item property, for example:
let selectedImage = UIImage(named: CatagoryPic[(indexPath as NSIndexPath).item])
selectedProductImg.image = selectedImage
}
#IBAction func sizeBtnPressed(_ sender: Any) {
sizeDropDown.show()
//print("size btn pressed")
}
#IBAction func colorBtnPressed(_ sender: Any) {
colorDropDown.show()
}
#IBAction func backBtn(_ sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
}
There are some other class where I am facing the same issue. Hope If I get this solution I will be able to solve those. Thanks in advance .
First thing to note is that you are returning the size value and not color in the product_color variable.
Also when you loop through the arrays in your JSON, you're setting the variable to only be the final value. For example here:
let sizeData = items["size"].arrayValue
for itemsIMG in sizeData {
self._size = itemsIMG["size_data"].stringValue
}
The JSON is
"size": [
{
"size_data": "L"
},
{
"size_data": "S"
}
]
So _size is going to be set as "S" and "L" will never be assigned. I'd suggest changing _size, _color and _image to
var _sizes: [String] = []
var _colors: [String] = []
var _images: [String] = []
And then when looping through the JSON array:
let sizeData = items["size"].arrayValue
for itemsIMG in sizeData {
let size = itemsIMG["size_data"].stringValue
_sizes.append(size)
}
If I'm understanding correctly, you then want to update your dropdown data when you get the response with the gist.
sizeDropDown.dataSource = product_detail.product_sizes
Then the same for the other drop downs.
And create product_sizes, product_colors and product_images variables in the model similar sale_price and regular_price.

swift do task after the preview task is finished

In my swift 2 app i would like to make an http-post-request to get data of my mysql database. After that I would like to write this data into my core data.
I have the code part for request and write.
How do I get the following structure.
Request Data
if request data complete write in into core data
Could this be the correct thing?
This is my SyncMYSQL function in my Sync.swift file.
class func SyncMYSQL() {
print("START SYNC MYSQL")
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let defaults = NSUserDefaults.standardUserDefaults()
let request = NSMutableURLRequest(URL: NSURL(string: "https://xxx")!)
request.HTTPMethod = "POST"
let postString = "userid=12"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
// if no internet connection
guard error == nil && data != nil else {
print("error=\(error)")
return
}
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)!
let fetchRequest = NSFetchRequest(entityName: "ITEMS")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try managedObjectContext!.executeFetchRequest(fetchRequest)
for managedObject in results {
let managedObjectData:NSManagedObject = managedObject as! NSManagedObject
managedObjectContext!.deleteObject(managedObjectData)
}
} catch let error as NSError {
print("Detele all data in ITEMS error : \(error) \(error.userInfo)")
}
var x = 0
while (x < responseString.count ) {
let newItem = NSEntityDescription.insertNewObjectForEntityForName("ITEMS", inManagedObjectContext: managedObjectContext!) as! CoreData_ITEMS
newItem.name = responseString[x]
x++
}
dispatch_sync(dispatch_get_main_queue(),{
print("FINISH MYSQL")
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
});
}
task.resume()
}
This function i call from my view controller like this way:
func RequestData() {
Sync.SyncMYSQL()
print ("Start Core Data")
let fetchRequest = NSFetchRequest(entityName: "ITEMS")
do {
try data = managedObjectContext!.executeFetchRequest(fetchRequest) as! [CoreData_ITEMS]
} catch { }
Table.reloadData()
}
You should use closure blocks as callback when the operation ends
class func SyncMYSQL(onSuccess: ()->()){
// your custom events
// your custom events
// your custom events
if MYSQLisSynced == true {
// when MYSQL is synced you can call the success block
onSuccess()
}
}
In other file when you call the function SyncMYSQL() you have to specify the on success block
SyncMYSQL { () -> () in
//custom callback actions
}