How to search a PDF using PDFkit in Swift - swift

My goal is to search for a string and then go to it. I have these three lines of code needed to implement it. I just know my thought process for getting there is missing something. I am not sure how to use .findstring. I read that it returns an array of PDFSelections. But I am not sure how to use that to use .setCurrentSelection using the PDFSelection array.
let found = document.findString(selection, withOptions: .caseInsensitive)
let stringSelection = page?.selection(for: NSRange(location:10, length:5))
pdfView.setCurrentSelection(stringSelection, animate: true)

Create SearchTableViewController ViewController :
import UIKit
import PDFKit
protocol SearchTableViewControllerDelegate: class {
func searchTableViewController(_ searchTableViewController: SearchTableViewController, didSelectSerchResult selection: PDFSelection)
}
class SearchTableViewController: UITableViewController {
open var pdfDocument: PDFDocument?
weak var delegate: SearchTableViewControllerDelegate?
var searchBar = UISearchBar()
var searchResults = [PDFSelection]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = 150
searchBar.delegate = self
searchBar.showsCancelButton = true
searchBar.searchBarStyle = .minimal
navigationItem.titleView = searchBar
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
target: self,
action: #selector(closeBtnClick))
tableView.register(UINib(nibName: "SearchViewCell", bundle: nil), forCellReuseIdentifier: "SearchViewCell")
}
#objc func closeBtnClick(sender: UIBarButtonItem) {
dismiss(animated: false, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
searchBar.becomeFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in 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 searchResults.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchViewCell", for: indexPath) as! SearchViewCell
let selection = searchResults[indexPath.row]
let page = selection.pages[0]
let outline = pdfDocument?.outlineItem(for: selection)
let outlintstr = outline?.label ?? ""
let pagestr = page.label ?? ""
let txt = outlintstr + " 页码: " + pagestr
cell.destinationLabel.text = ""
let extendSelection = selection.copy() as! PDFSelection
extendSelection.extend(atStart: 10)
extendSelection.extend(atEnd: 90)
extendSelection.extendForLineBoundaries()
let range = (extendSelection.string! as NSString).range(of: selection.string!, options: .caseInsensitive)
let attrstr = NSMutableAttributedString(string: extendSelection.string!)
attrstr.addAttribute(NSAttributedStringKey.backgroundColor, value: UIColor.yellow, range: range)
cell.resultTextLabel.attributedText = attrstr
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selection = searchResults[indexPath.row]
delegate?.searchTableViewController(self, didSelectSerchResult: selection)
dismiss(animated: false, completion: nil)
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchBar.resignFirstResponder()
}
}
extension SearchTableViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
pdfDocument?.cancelFindString()
dismiss(animated: false, completion: nil)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// if searchText.count < 2 {
// return
// }
searchResults.removeAll()
tableView.reloadData()
pdfDocument?.cancelFindString()
pdfDocument?.delegate = self
pdfDocument?.beginFindString(searchText, withOptions: .caseInsensitive)
}
}
extension SearchTableViewController: PDFDocumentDelegate {
func didMatchString(_ instance: PDFSelection) {
searchResults.append(instance)
tableView.reloadData()
}
}
On Search Button Tap :
let searchViewController = SearchTableViewController()
searchViewController.pdfDocument = self.pdfdocument
searchViewController.delegate = self
let nav = UINavigationController(rootViewController: searchViewController)
self.present(nav, animated: false, completion:nil)
You will get highlighted text in :
func searchTableViewController(_ searchTableViewController: SearchTableViewController, didSelectSerchResult selection: PDFSelection) {
selection.color = UIColor.yellow
self.pdfview.currentSelection = selection
self.pdfview.go(to: selection)
calculateStandByMood()
}
Just add this protocol in pdfViewController :
SearchTableViewControllerDelegate

I think you can reach to the current selection using
pdfView.go(to selection: pdView.currentSelection)
According to this doc, PDF can be navigated using selection, destination and rect
https://developer.apple.com/documentation/pdfkit/pdfview/1505172-go

Related

want to display data on Home screen from searched data coming from api

I am making an app in which i have a home screen which lists categories and store data into tableview. in tableview i have a search button which navigates me to search screen when clicked and on search screen i have taken custom textfield to search. Now i want that when i search something and it shows in result then when i click on searched result then it should show data of the category or store selected on previous home screen. And the search result is category data and store data. All data is coming from api and am using tableview to show search data
screenshot for coupons api in which search data will be display:
screenshot for search api:
code for my search screen is as below:
class SearchPageController: UIViewController {
#IBOutlet weak var searchTxtBar: UITextField!
#IBOutlet weak var searchTblView: UITableView!
var searchData = [ModelSearched]()
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
// Do any additional setup after loading the view.
searchTxtBar.delegate = self
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
//MARK: IBActions
#IBAction func toHomeScreen(_ sender: UIButton) {
self.navigationController?.popViewController(animated: true)
}
func getSearchList(){
let params: Parameters=[
"search":searchTxtBar.text!,
]
if ApiUtillity.sharedInstance.isReachable()
{
ApiUtillity.sharedInstance.StartProgress(view: self.view)
APIClient<ModelBaseSearchList>().API_GET(Url: SD_GET_SearchList, Params: params as [String:AnyObject], Authentication: true, Progress: true, Alert: true, Offline: false, SuperVC: self, completionSuccess: { (modelResponse) in
ApiUtillity.sharedInstance.StopProgress(view: self.view)
if(modelResponse.success == true) {
self.searchData.removeAll()
let resul_array_tmp_new = modelResponse.searched! as NSArray
if resul_array_tmp_new.count > 0 {
for i in modelResponse.searched! {
self.searchData.append(i)
}
}
}
else {
self.view.makeToast(modelResponse.message)
}
ApiUtillity.sharedInstance.StopProgress(view: self.view)
self.searchTblView.reloadData()
}) { (failed) in
ApiUtillity.sharedInstance.StopProgress(view: self.view)
self.view.makeToast(failed.localizedDescription)
}
}
else
{
self.view.makeToast("No Internet Connection..")
}
}
#IBAction func clearSearchData(_ sender: UIButton) {
self.searchData.removeAll()
self.searchTxtBar.text = ""
searchTblView.reloadData()
}
}
//MARK: Tableview delegates and datasource methods
extension SearchPageController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "catstoredata")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "catstoredata")
}
let dict = searchData[indexPath.row]
cell?.selectionStyle = .none
cell?.textLabel?.text = dict.name
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
self.navigationController?.popViewController(animated: true)
}
}
//MARK: textfield delegates method
extension SearchPageController: UITextFieldDelegate{
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.searchTxtBar.resignFirstResponder()
self.searchTblView.reloadData()
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.getSearchList()
}
}
code for my home screen is as below:
class HomeViewController: UIViewController {
var couponsData = [ModelCoupons]()
var colorList = [UIColor]()
var selectedIds = [Int]()
#IBOutlet var logoutPopup: UIView!
#IBOutlet weak var homeTblView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//append colors
colorList.append(UIColor(rgb: 0xdd191d))
colorList.append(UIColor(rgb: 0xd81b60))
colorList.append(UIColor(rgb: 0x8e24aa))
colorList.append(UIColor(rgb: 0x5e35b1))
colorList.append(UIColor(rgb: 0x3949ab))
colorList.append(UIColor(rgb: 0x4e6cef))
colorList.append(UIColor(rgb: 0x00acc1))
colorList.append(UIColor(rgb: 0x00897b))
colorList.append(UIColor(rgb: 0x0a8f08))
colorList.append(UIColor(rgb: 0x7cb342))
colorList.append(UIColor(rgb: 0xc0ca33))
colorList.append(UIColor(rgb: 0xfdd835))
colorList.append(UIColor(rgb: 0xfb8c00))
colorList.append(UIColor(rgb: 0xf4511e))
colorList.append(UIColor(rgb: 0xf4511e))
colorList.append(UIColor(rgb: 0x757575))
colorList.append(UIColor(rgb: 0x546e7a))
self.homeTblView.register(UINib(nibName: "HomeCell", bundle: nil), forCellReuseIdentifier: "HomeCell")
self.homeTblView.register(UINib(nibName: "Home1Cell", bundle: nil), forCellReuseIdentifier: "Home1Cell")
self.post_CouponsData()
self.homeTblView.reloadData()
print(selectedIds)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
func changeDateForamte(dateString: String, currentDateFormate: String, newDateFormate: String) -> String
{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = currentDateFormate
let newDate = dateFormatter.date(from: dateString)
dateFormatter.dateFormat = newDateFormate
return dateFormatter.string(from: newDate!)
}
#IBAction func logout_yes(_ sender: Any) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
self.navigationController?.pushViewController(vc, animated: true)
}
#IBAction func logout_no(_ sender: Any) {
self.logoutPopup.removeFromSuperview()
}
#objc func openDeals(sender: UIButton) {
let svc = SFSafariViewController(url: URL(string: couponsData[sender.tag].guid!)!)
self.present(svc, animated: true, completion: nil)
}
//MARK: IBActions
#IBAction func toCategoryScreen(_ sender: UIButton) {
self.navigationController?.popViewController(animated: true)
}
#IBAction func toSearchPage(_ sender: UIButtonX) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "SearchPageController") as! SearchPageController
self.navigationController?.pushViewController(vc, animated: true)
}
#IBAction func logoutBtnPressed(_ sender: Any) {
ApiUtillity.sharedInstance.AddSubViewtoParentView(parentview: self.view, subview: logoutPopup)
}
func post_CouponsData() {
if ApiUtillity.sharedInstance.isReachable() {
var params = [String : String]()
params ["term_ids"] = "\(self.selectedIds.map(String.init).joined(separator: ","))"
ApiUtillity.sharedInstance.StartProgress(view: self.view)
APIClient<ModelBaseCouponsList>().API_POST(Url: SD_POST_CouponsList, Params: params as [String:AnyObject], Authentication: true, Progress: true, Alert: true, Offline: false, SuperVC: self, completionSuccess: { (modelResponse) in
ApiUtillity.sharedInstance.StopProgress(view: self.view)
if(modelResponse.success == true) {
ApiUtillity.sharedInstance.StopProgress(view: self.view)
let dict = modelResponse.coupons
for i in dict! {
self.couponsData.append(i)
}
}else {
self.view.makeToast(modelResponse.message)
}
ApiUtillity.sharedInstance.StopProgress(view: self.view)
self.homeTblView.reloadData()
}) { (failed) in
self.view.makeToast(failed.localizedDescription)
ApiUtillity.sharedInstance.StopProgress(view: self.view)
}
}else {
self.view.makeToast("No internet connection...")
ApiUtillity.sharedInstance.StopProgress(view: self.view)
}
}
}
extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return couponsData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if !couponsData[indexPath.row].postImage!.isEmpty {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeCell", for: indexPath) as! HomeCell
let dict = couponsData[indexPath.row]
if let postTitle = dict.postTitle, postTitle.count != 0 {
cell.ticket_postTitle.text = postTitle
}
if let postContent = dict.postContent, postContent.count != 0 {
cell.ticket_postContent.text = postContent
}
if let storeName = dict.stores, storeName.count != 0 {
cell.storename.text = storeName
}
cell.home_image.image = UIImage(named: dict.postImage!)
cell.ticketImageView.tintColor = colorList[indexPath.row]
cell.ticket_ValidDate.text = self.changeDateForamte(dateString: dict.validTill!, currentDateFormate: "yyyy-MM-dd", newDateFormate: "dd MMMM yyyy")
// cell.ticketImageView.tintColor = .random()
cell.goToDealBtn.tag = indexPath.row
cell.goToDealBtn.addTarget(self, action: #selector(self.openDeals), for: .touchUpInside)
return cell
}else {
let cell = tableView.dequeueReusableCell(withIdentifier: "Home1Cell", for: indexPath) as! Home1Cell
let dict = couponsData[indexPath.row]
if let postTitle = dict.postTitle, postTitle.count != 0 {
cell.ticket_postTitle.text = postTitle
}
if let postContent = dict.postContent, postContent.count != 0 {
cell.ticket_postContent.text = postContent
}
if let storeName = dict.stores, storeName.count != 0 {
cell.storename.text = storeName
}
cell.ticketImageView.tintColor = colorList[indexPath.row]
cell.ticket_ValidDate.text = self.changeDateForamte(dateString: dict.validTill!, currentDateFormate: "yyyy-MM-dd", newDateFormate: "dd MMMM yyyy")
// cell.ticketImageView.tintColor = .random()
cell.goToDealBtn.tag = indexPath.row
cell.goToDealBtn.addTarget(self, action: #selector(self.openDeals), for: .touchUpInside)
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if !couponsData[indexPath.row].postImage!.isEmpty {
return 339.0
}else {
return 234.0
}
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if !couponsData[indexPath.row].postImage!.isEmpty {
return 339.0
}else {
return 234.0
}
}
}

Autolayout Contraints Changing When View Is Reloaded - Swift

recently in the search VC of my app I have been having a major problem with the contraints for weeks. I've redone them 4 times now with no improvement. My tableview cell is not complex -- an imageView located at the left and two labels on top of each other. Most cells size fine. The only thing is that(without pattern) two cells are randomly messed up(the labels and imageView move way out of place). You can see here:
Here is a picture of the constraints for the vc:
The strange thing is that this sometimes occurs when I toggle the scopeButtons up top. I looked in my code to see if I could fix this when the scopeButton reloads the tableview, but I could not find a problematic instance. My code is below:
import UIKit
import Firebase
import Kingfisher
class SearchPostsController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var idArray:[String] = []
var detailViewController: DetailViewController? = nil
var candies = [Person]()
var filteredCandies = [Person]()
let searchController = UISearchController(searchResultsController: nil)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = false
//initiate the search bar that appears up top when view is segued to
}
if let selectionIndexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectionIndexPath, animated: animated)
}
self.tableView.reloadData()
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = true
}
}
override func viewDidLoad() {
super.viewDidLoad()
Database.database().reference()
.child("\(UserData().mySchool!)/posts")
.queryOrderedByKey()//keys were out of order, so we have to use this to help
.observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.childrenCount)
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.key)
self.idArray.append(child.key)
}
var reversedNames = [String]()
for arrayIndex in 0..<self.idArray.count {
reversedNames.append(self.idArray[(self.idArray.count - 1) - arrayIndex])
//reverse names so we dont have to sort the cells by date
}
for x in reversedNames{
self.searchNames(id: x)//get names from the ids here, tada!!!
self.tableView.reloadData()
}
})
//self.tableView.reloadData()//without this, the results wouldnt show up right away
searchController.searchBar.setScopeBarButtonTitleTextAttributes([NSAttributedStringKey.foregroundColor.rawValue: UIColor.white], for: .normal)
searchController.searchBar.scopeButtonTitles = ["Posts", "Users"]
searchController.searchBar.delegate = self
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search posts or usernames"
searchController.searchBar.showsScopeBar = true
navigationItem.searchController = searchController
definesPresentationContext = true
if let splitViewController = splitViewController {
let controllers = splitViewController.viewControllers
detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 74
}
// override func viewWillDisappear(_ animated: Bool) {
// candies.removeAll()
// }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DAY69:GRADUATION DAY", for: indexPath) as! SeachCell
cell.cellImageVIew.frame.size.width = 48
cell.cellImageVIew.frame.size.height = 48
let personUser: Person
if isFiltering() {
personUser = filteredCandies[indexPath.row]
} else {
personUser = candies[indexPath.row]
}
//PERSON USER IS IMPORTANT!!!!! ^^^
cell.nameLabel.text = personUser.name.removingPercentEncoding
cell.messageLabel.text = personUser.category.removingPercentEncoding
let name = personUser.name
Database.database().reference().child("users/\(name)/profileImageURL").observe(.value, with: { (snapshot) in
let profURL = "\(snapshot.value!)"
let profIRL = URL(string: profURL)
//set up imageview
cell.cellImageVIew.layer.borderWidth = 1
cell.cellImageVIew.layer.masksToBounds = false
cell.cellImageVIew.layer.borderColor = UIColor.black.cgColor
cell.cellImageVIew.layer.cornerRadius = cell.cellImageVIew.frame.height/2
cell.cellImageVIew.clipsToBounds = true
cell.cellImageVIew.contentMode = .scaleAspectFill
cell.cellImageVIew.kf.indicatorType = .activity
cell.cellImageVIew.kf.setImage(with: profIRL)
})
//TODO: make an extension of imageview to do all this for me. It's getting to be ridiculous
return cell
}
func searchNames(id: String){
// var message = String()
// var name = String()
Database.database().reference().child("\(UserData().mySchool!)/posts/\(id)/message").observe(.value, with: { (snapshot) in
// message = snapshot.value as! String
Database.database().reference().child("\(UserData().mySchool!)/posts").child("\(id)/username").observe(.value, with: { (username) in
// name = username.value as! String
let user = Person(category: "\(snapshot.value!)", name: "\(username.value!)", id: id)
self.candies.append(user)
print( "\(snapshot.value!)", "\(username.value!)")
self.tableView.reloadData()
})
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return filteredCandies.count
}
return candies.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
tableView.estimatedRowHeight = 74.0
tableView.rowHeight = UITableViewAutomaticDimension
return UITableViewAutomaticDimension
}
// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
// return 74
// }
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "DAY69:GRADUATION DAY", for: indexPath) as! SeachCell
cell.cellImageVIew.frame.size.width = 48
cell.cellImageVIew.frame.size.height = 48
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles?[searchBar.selectedScopeButtonIndex]
if scope == "Users"{
let username = candies[indexPath.row].name
print(username)
self.tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "userClicked", sender: username)
}
if scope == "Posts"{
let post = candies[indexPath.row].category
let user = candies[indexPath.row].name
let id = candies[indexPath.row].id
print(post)
let defaults = UserDefaults.standard
defaults.set(id, forKey: "ID")
let def2 = UserDefaults.standard
def2.set(post, forKey: "Post")
def2.set(user, forKey: "USER")
self.tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "postCellTapped", sender: nil)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//__________tbv methods above________________________________________________
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredCandies = candies.filter({(candy : Person) -> Bool in
let doesCategoryMatch = (scope == "Posts") || (scope == "Users")
print(searchText)
if searchBarIsEmpty() {
return doesCategoryMatch
}
if scope == "Users"{
return doesCategoryMatch && candy.name.lowercased().contains(searchText.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!.lowercased())
}
else{
return doesCategoryMatch && candy.category.lowercased().contains(searchText.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!.lowercased())
}
})
tableView.reloadData()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let personalUser: Person
if isFiltering() {
personalUser = filteredCandies[indexPath.row]
} else {
personalUser = candies[indexPath.row]
}
}
}
if segue.identifier == "userClicked" {
if let nextView = segue.destination as? UserProfileController {
nextView.selectedUser = "\(sender!)"
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func isFiltering() -> Bool {
let searchBarScopeIsFiltering = searchController.searchBar.selectedScopeButtonIndex != 0
return searchController.isActive && (!searchBarIsEmpty() || searchBarScopeIsFiltering)
}
}
extension SearchPostsController: UISearchResultsUpdating {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
}
extension SearchPostsController: UISearchBarDelegate {
// MARK: - UISearchBar Delegate
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
Any help would be greatly appreciated in trying to fix the constraints! Thanks!

How to display search results, and no result text if no result?

I am trying to display only results instead of all data from the parse server. I tried different methods (mostly same), but I failed. I know that I miss something.
My question is how to display only results as well as no result text on the screen if no result.
You can find my approach below.
Thank you very much in advance.
class SearchTableViewController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
#IBOutlet weak var searchBar: UISearchBar!
var searchActive : Bool = false
var data:[PFObject]!
var noteObjects: NSMutableArray! = NSMutableArray()
var filtered:[PFObject]!
var refresher: UIRefreshControl = UIRefreshControl()
var searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
search()
searchBar.delegate = self
self.refresher.addTarget(self, action: #selector(refresh), for: .valueChanged)
self.tableView?.addSubview(refresher)
self.searchDisplayController?.searchResultsTableView.rowHeight = tableView.rowHeight;
}
#objc func refresh() {
print("Refreshed")
self.refresher.endRefreshing()
search()
}
#objc func search(searchText: String? = nil){
let query = PFQuery(className: "NewUsers")
query.whereKey("user", equalTo: PFUser.current() as Any)
if(searchText != nil){
query.whereKey("name", contains: searchText)
}
query.findObjectsInBackground { (objects, error) in
if error == nil {
if let cars = objects {
for object in cars {
if (object as PFObject?) != nil {
self.data = objects
self.tableView.reloadData()
}
}
}
}
}
}
func updateSearchResults(for searchController: UISearchController) {
self.filtered.removeAll(keepingCapacity: false)
let searchText = searchController.searchBar.text
let query: PFQuery = PFQuery(className: "NewUsers")
if searchController.isActive == true {
query.whereKey("name", matchesRegex: searchText!, modifiers: "i")
self.tableView.reloadData()
}
query.findObjectsInBackground { (results, error) in
self.filtered = results
self.tableView.reloadData()
}
print(searchText as Any)
}
var shouldUseSearchResult : Bool {
if let searchText = searchController.searchBar.text {
if searchText.isEmpty {
return false
}
}
return searchController.isActive
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if data != nil {
return self.data.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SearchTableViewCell
let obj = self.data[indexPath.row]
self.name = (obj["name"] as? String)!
self.surname = (obj["surname"] as? String)!
self.birthdate = (obj["birthday"] as? String)!
self.age = (obj["age"] as? String)!
self.city = (obj["city"] as? String)!
cell.dateLabel.text = ": " + birthday
cell.ageLabel.text = ": \(age)"
cell.userLabel.text = ": " + name + " " + surname
return cell
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchActive = true;
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchActive = false;
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchActive = false;
self.searchBar.endEditing(true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchActive = false;
self.searchBar.endEditing(true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
search(searchText: searchText)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
self.searchBar.resignFirstResponder()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
if (self.data.count == 0)
{
let noDataLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: tableView.bounds.size.height))
noDataLabel.text = "No Result Found."
noDataLabel.textColor = UIColor.red
noDataLabel.textAlignment = .center
self.tableView.backgroundView = noDataLabel
self.tableView.backgroundColor = UIColor.white
self.tablevİEW.separatorStyle = .none
}
return 1
}
self.arrayDealPage is array & self.dealsTable is tableView Hope, it might help u and delegate and datasource protocol added.

infinite loop when selected programmatically a cell

I have a tableview with a textfield in every row.
I need to reload the tableview and programmatically select the row the user had selected.
The user can write what he wants. The data will be deleted when the textfield's editing has ended and added when the textfield has begun editing.
But I get a infinite loop. Please cloud you help me?
My code :
import UIKit
class OptionsItemViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var textFiedlDelegate: UITextField? = nil
var categorySelected: Category?
var options: [String] = []
var nameOptions: [String] = []
var cellSelected: Int = 0
var viewHeight: CGFloat = 0
var selectedRow: IndexPath? = nil
var tableviewNeedToReload: Bool = false
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var keyboardAlwaysShow: UITextField!
#IBOutlet weak var newFeatureButton: UIBarButtonItem!
private let db = DataBase()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.textFiedlDelegate?.delegate = self
self.title = categorySelected!.name
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
db.getItemOptions(predicateFormat: "id == \(self.categorySelected!.id)", completion: { results in
self.categorySelected = results.first!
self.options = self.categorySelected!.options as! [String]
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
self.viewHeight = self.view.frame.size.height
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(false)
var index = 0
while index < self.options.count {
if self.options[index] != "" {
index += 1
} else {
self.options.remove(at: index)
}
db.setCategoryOptions(category: self.categorySelected!, options: self.options, index: cellSelected)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
#IBAction func newFeature(_ sender: Any) {
if self.options.last != "" {
let indexPath: IndexPath = IndexPath(row: self.options.count, section: 0)
self.options.append("")
self.tableView.reloadData()
let cell = tableView(self.tableView, cellForRowAt: indexPath) as! CellItemOptions
cell.nameOptionsItem.becomeFirstResponder()
}
}
// MARK: - TableView Functions
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let option = options[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: CellItemOptions.identifier, for: indexPath) as! CellItemOptions
cell.nameOptionsItem.delegate = self
cell.configureCell(with: option)
return cell
}
func textFieldDidBeginEditing(_ textField: UITextField) {
self.cellSelected = options.index(of: textField.text!)!
let indexPath: IndexPath = IndexPath(row: self.cellSelected, section: 0)
self.tableView.reloadData()
let cell = self.tableView.cellForRow(at: indexPath) as! CellItemOptions
cell.nameOptionsItem.becomeFirstResponder()
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text! == "" {
if self.options[cellSelected] != "" {
db.setRemoveDetailsItem(category: self.categorySelected!, index: cellSelected)
}
self.options.remove(at: cellSelected)
} else {
self.options[cellSelected] = "\(textField.text!)"
db.setAddDetailsItem(category: self.categorySelected!, index: cellSelected)
}
db.setCategoryOptions(category: self.categorySelected!, options: self.options, index: cellSelected)
}
// MARK: - Keyboard
func keyboardWillShow(_ notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.size.height == self.viewHeight {
self.view.frame.size.height -= keyboardSize.height
}
}
}
func keyboardWillHide(_ notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != self.viewHeight {
self.view.frame.size.height += keyboardSize.height
}
}
}
}
class CellItemOptions: UITableViewCell {
static let identifier = "OptionsItemCell"
#IBOutlet weak var nameOptionsItem: UITextField!
private let tableView = OptionsItemViewController()
func configureCell(with cell: String) {
nameOptionsItem.text = cell
}
}
EDIT :
The loop is due to the reload data...
Like I reload data in textFieldDidBeginEditing(), the view is reloaded more and more ... And I need to textFieldDidBeginEditing() to know the row selected by the user.

tableView.reloadData() causing a crash of the app

In the Swift app, I present an addition view to add an element to the CoreData database. If I call tableview.reloadData(), the app crashes on the + button on the main screen. If I omit the reload data then the add view is presented and the data is added to the CoreData file.
the main view, from configureCell down:
func configureCell(cell: TransectTableViewCell, indexPath:NSIndexPath) {
let transectEntry = fetchedResultController.objectAtIndexPath(indexPath) as! Transects
cell.transectNameLabel.text = transectEntry.transectName
cell.transectNameLabel.textColor = UIColor.blackColor()
cell.transectNameLabel.shadowColor = UIColor.whiteColor()
cell.transectNameLabel.shadowOffset = CGSizeMake(1, 1)
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let countEntry = fetchedResultController.objectAtIndexPath(indexPath) as! Transects
coreDataStack.context.deleteObject(countEntry)
coreDataStack.saveContext()
}
}
func tableView(tableView: UITableView,
heightForRowAtIndexPath indexPath: NSIndexPath)
-> CGFloat {
return 50;
}
func didFinishViewController(viewController: AddTransectViewController, didSave: Bool) {
if didSave {
var error: NSError? = nil
let context = viewController.context
self.coreDataStack.saveContext()
}
dismissViewControllerAnimated(true, completion: {})
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "addTransectSegue" {
let newTransectViewController = segue.destinationViewController as! AddTransectViewController
let transectEntryEntity = NSEntityDescription.entityForName("Transects", inManagedObjectContext: coreDataStack.context)
let newTransectEntry = Transects(entity: transectEntryEntity!, insertIntoManagedObjectContext: coreDataStack.context)
newTransectViewController.transectNewEntry = newTransectEntry
newTransectViewController.context = newTransectEntry.managedObjectContext
newTransectViewController.delegate = self
}
if segue.identifier == "transectTasksSegue" {
let indexPath = tableView.indexPathForSelectedRow()!
let transectSelected = fetchedResultController.objectAtIndexPath(indexPath) as! Transects
let tasksViewController = segue.destinationViewController as! TransectTasksViewController
tasksViewController.coreDataStack = coreDataStack
tasksViewController.selectedTransect = transectSelected
}
}
func controllerDidChangeContent(controller:
NSFetchedResultsController) {
tableView.reloadData()
}
The addition view is:
import UIKit
import CoreData
import Foundation
protocol TransectDelegate {
func didFinishViewController(ViewController:AddTransectViewController, didSave:Bool)
}
class AddTransectViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var transectNameTextField: UITextField!
#IBOutlet weak var latitudeTextField: UITextField!
#IBOutlet weak var longitudeTextField: UITextField!
#IBOutlet weak var altitudeTextField: UITextField!
var transectNewEntry: Transects!
var context: NSManagedObjectContext!
var delegate:TransectDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func updateTransectEntry() {
if let entry = transectNewEntry {
entry.transectName = transectNameTextField.text
entry.latitude = latitudeTextField.text
entry.longitude = longitudeTextField.text
entry.altitude = altitudeTextField.text
}
}
#IBAction func cancelButtonWasTapped(sender: AnyObject) {
delegate?.didFinishViewController(self, didSave: false)
}
#IBAction func saveButtonWasTapped(sender: AnyObject) {
updateTransectEntry()
delegate?.didFinishViewController(self, didSave: true)
}
}
I am missing something, but cannot see what. Ideas would be welcome.
The app hangs up on cell.transectNameLabel.text = transectEntry.transectName
with: Thread 1:EXC_BAD_ACCESS (code=1, address=0x0)
My real confusion is that this works perfectly:
import UIKit
import CoreData
class PlantSpeciesViewController: UIViewController, NSFetchedResultsControllerDelegate, PlantSpeciesDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView:UITableView!
var coreDataStack: CoreDataStack!
lazy var fetchedResultController:
NSFetchedResultsController = self.plantSpeciesFetchedResultsController()
var plantSpecies: PlantSpecies!
var selectedFamily: PlantFamily!
var context: NSManagedObjectContext!
var plantFamilyName: String!
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundColor = UIColor.clearColor()
view.backgroundColor = UIColor(patternImage: UIImage (named: "Monitor backdrop.png")!)
self.title = plantFamilyName
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func plantSpeciesFetchedResultsController()
->NSFetchedResultsController {
fetchedResultController =
NSFetchedResultsController(
fetchRequest: plantSpeciesFetchRequest(),
managedObjectContext: coreDataStack.context,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultController.delegate = self
var error: NSError? = nil
if (!fetchedResultController.performFetch(&error)){
println("Error: \(error?.localizedDescription)")
abort()
}
return fetchedResultController
}
func plantSpeciesFetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "PlantSpecies")
fetchRequest.fetchBatchSize = 20
let predicate = NSPredicate(format: "familyName == %#", selectedFamily)
fetchRequest.predicate = predicate
let sortDescriptor = NSSortDescriptor(key: "plantSpecies", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
//var error: NSError?
return fetchRequest
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultController.sections!.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultController.sections![section].numberOfObjects
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("plantSpeciesCell", forIndexPath: indexPath) as! PlantSpeciesTableViewCell
cell.backgroundColor = UIColor.clearColor()
configureCell(cell, indexPath: indexPath)
return cell
}
func configureCell(cell: PlantSpeciesTableViewCell, indexPath:NSIndexPath) {
let plantEntry = fetchedResultController.objectAtIndexPath(indexPath) as! PlantSpecies
cell.speciesNameLabel.text = plantEntry.plantSpecies
cell.speciesNameLabel.textColor = UIColor.blackColor()
cell.speciesNameLabel.shadowColor = UIColor.whiteColor()
cell.speciesNameLabel.shadowOffset = CGSizeMake(1, 1)
cell.speciesImageView.image = UIImage (data: plantEntry.plantImage)
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let countEntry = fetchedResultController.objectAtIndexPath(indexPath) as! PlantFamily
coreDataStack.context.deleteObject(countEntry)
coreDataStack.saveContext()
}
}
func tableView(tableView: UITableView,
heightForRowAtIndexPath indexPath: NSIndexPath)
-> CGFloat {
return 90;
}
func didFinishViewController(viewController: AddPlantSpeciesViewController, didSave: Bool) {
if didSave {
var error: NSError? = nil
let context = viewController.context
self.coreDataStack.saveContext()
}
dismissViewControllerAnimated(true, completion: {})
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "addSpeciesSegue" {
let newPlantViewController = segue.destinationViewController as! AddPlantSpeciesViewController
let plantEntryEntity = NSEntityDescription.entityForName("PlantSpecies", inManagedObjectContext: coreDataStack.context)
let newSpeciesEntry = PlantSpecies(entity: plantEntryEntity!, insertIntoManagedObjectContext: coreDataStack.context)
newPlantViewController.selectedFamily = selectedFamily
newPlantViewController.plantNameEntry = newSpeciesEntry
newPlantViewController.context = newSpeciesEntry.managedObjectContext
newPlantViewController.delegate = self
}
}
func controllerDidChangeContent(controller:
NSFetchedResultsController) {
tableView.reloadData()
}
}
coupled with:
import UIKit
import CoreData
import Foundation
protocol PlantSpeciesDelegate {
func didFinishViewController(ViewController:AddPlantSpeciesViewController, didSave:Bool)
}
class AddPlantSpeciesViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet weak var plantNameTextField: UITextField!
#IBOutlet weak var plantImageView: UIImageView!
#IBOutlet weak var imageSwitch: UISwitch!
#IBOutlet weak var imageFromFileButton: UIButton!
#IBOutlet weak var imageFromCameraButton: UIButton!
let imagePicker = UIImagePickerController()
var plantNameEntry: PlantSpecies!
var selectedFamily: PlantFamily!
var passedPlantFamily: String!
var newPlantName: String!
var newImageData: NSData!
var context: NSManagedObjectContext!
var delegate:PlantSpeciesDelegate?
override func viewDidLoad() {
super.viewDidLoad()
plantImageView.image = UIImage(named: "placeholder image.jpg")
imagePicker.delegate = self
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func imageSourceSwitch(sender: AnyObject) {
if imageSwitch.on == true
{
self.imageFromFileButton.enabled = true
self.imageFromCameraButton.enabled = false
}
else
{
self.imageFromCameraButton.enabled = true
self.imageFromFileButton.enabled = false
}
}
#IBAction func imageFromFile(sender: AnyObject) {
imagePicker.sourceType = .PhotoLibrary
presentViewController(imagePicker, animated: true, completion: nil)
}
#IBAction func imageFromCamera(sender: AnyObject) {
imagePicker.sourceType = .Camera
presentViewController(imagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
self.plantImageView.image = image
dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func getPlantName() {
newPlantName = plantNameTextField.text
plantNameTextField.resignFirstResponder()
}
func updateSpeciesEntry() {
if let entry = plantNameEntry {
entry.plantSpecies = newPlantName
entry.plantImage = UIImageJPEGRepresentation(plantImageView.image, 1.0)
entry.familyName = selectedFamily
}
}
#IBAction func cancelButtonWasTapped(sender: AnyObject) {
delegate?.didFinishViewController(self, didSave: false)
}
#IBAction func saveButtonWasTapped(sender: AnyObject) {
updateSpeciesEntry()
delegate?.didFinishViewController(self, didSave: true)
}
}
So, what is the difference?
This happen to me too, fixed it with:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
instead of just tableView.reloadData()
, cause it seems that it was being called from wrong thread.
The reason for the crash is most likely that the table cannot load the data, for example a value does not exist and is being force unwrapped. The crash only happens, therefore, when you try to collect the data. Check through all the values to be sure.
I'd like to expand on GJZ answer & what brought me here. I have UITableViewCells that have a textfield. I attempted to get values the user entered from the field.
let companyNameCell: TextEntryCell = self.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as! TextEntryCell
what I realized is that when I dynamically hide and showed rows by changing the cell heights using the tableview.reloadrows method, the app would crash if it attempted to read a cell the user could not see on their screen. that is because the cells I think were deallocated.