I have a delegate which I use to trigger next page of a Pageview controller and I have 6 viewcontrollers attached to the page viewcontroller. after the first 3 calls in 3 different controllers, the delegate stops getting called and as such, the next page of the page controller is not triggered, beleow is my code which works for first 3 and stops getting called after the first 3
This Button tap code is in 5 of the viewcontrollers with pageIndex set in each 1..5
weak var delegate: NextDelegate?
nextBtn.rx.tap.asDriver().drive(onNext: {
guard let delegate = self.delegate else {return}
delegate.next(pageIndex: 1)
}).disposed(by: disposeBag)
backBtn.rx.tap.asDriver().drive(onNext: {
guard let delegate = self.delegate else {return}
delegate.previous(pageIndex: 1)
}).disposed(by: disposeBag)
My Protocol and Methods
lazy var controllers: [UIViewController] = {
let locVC = LocationVC()
locVC.delegate = self
let typeVC = TypeVC()
typeVC.delegate = self
let descVC = DescVC()
descVC.delegate = self
let priceVC = PriceVC()
descVC.delegate = self
let featuresVC = FeaturesVC()
featuresVC.delegate = self
let picturesVC = PicturesVC()
picturesVC.delegate = self
return [locVC,
typeVC, descVC, priceVC, featuresVC, picturesVC]
}()
func backBtnClicked(index: Int) {
guard index - 1 >= 0 else { return }
pageController.setViewControllers([controllers[index - 1]], direction: .reverse, animated: false, completion: nil)
}
func nextBtnClicked(index: Int) {
log("\(controllers.count)", .happy)
guard index + 1 < controllers.count else { return }
pageController.setViewControllers([controllers[index + 1]], direction: .forward, animated: false, completion: nil)
}
extension ViewController: NextDelegate {
func next(pageIndex: Int) {
print("nexteddddd \(pageIndex)")
nextBtnClicked(index: pageIndex)
}
func previous(pageIndex: Int) {
print("backedddd \(pageIndex)")
backBtnClicked(index: pageIndex)
}
}
protocol NextDelegate: AnyObject {
func next(pageIndex: Int)
func previous(pageIndex: Int)
}
The problem may relates to the fact that you set a static index 1 here
nextBtn.rx.tap.asDriver().drive(onNext: {
guard let delegate = self.delegate else {return}
delegate.next(pageIndex: 1)
}).disposed(by: disposeBag)
backBtn.rx.tap.asDriver().drive(onNext: {
guard let delegate = self.delegate else {return}
delegate.previous(pageIndex: 1)
}).disposed(by: disposeBag)
instead you need to have an index var in each vc and assign it when instantiate the vc so you can use it above or a vaibale in the main pager
Fix it
var current = 0 // assuming you set first vc initially
nextBtn.rx.tap.asDriver().drive(onNext: {
guard let delegate = self.delegate else {return}
delegate.next(pageIndex:self.current)
}).disposed(by: disposeBag)
backBtn.rx.tap.asDriver().drive(onNext: {
guard let delegate = self.delegate else {return}
delegate.previous(pageIndex:self.current)
}).disposed(by: disposeBag)
func backBtnClicked(index: Int) {
guard index - 1 >= 0 else { return }
self.current = index - 1
pageController.setViewControllers([controllers[index - 1]], direction: .reverse, animated: false, completion: nil)
}
func nextBtnClicked(index: Int) {
log("\(controllers.count)", .happy)
guard index + 1 < controllers.count else { return }
self.current = index + 1
pageController.setViewControllers([controllers[index + 1]], direction: .forward, animated: false, completion: nil)
}
Related
I have a Tab bar Controller to manage all the views. The problem I'm having is when I call a function in searchViewController (doAThing()) from HomeViewController which reloads the tableView in searchViewController, the tableView is empty when the Tab bar controller switches views.
Why does calling the doAThing method in my searchViewController not refresh my tableView?
How can I fill my tableView with values.
HomeViewController.swift
import UIKit
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UISearchBarDelegate{
#IBOutlet weak var productCollectionView: UICollectionView!
#IBOutlet weak var storesCollectionView: UICollectionView!
#IBOutlet var searchQ: UISearchBar!
#IBOutlet weak var scrollView: UIScrollView!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(collectionView == storesCollectionView) {
return storesImages.count
}
return productsImages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(collectionView == storesCollectionView) {
let cell2 = storesCollectionView.dequeueReusableCell(withReuseIdentifier: "storesCell", for: indexPath) as! StoreCollectionViewCell
cell2.compstoreImage.image = UIImage(named: storesImages[indexPath.row])
return cell2
}
else{
let cell = productCollectionView.dequeueReusableCell(withReuseIdentifier: "productsCell", for: indexPath) as! ProductCollectionViewCell
cell.pillImage.image = UIImage(named: productsImages[indexPath.row])
return cell
}
}
var productsImages:[String] = ["pcPic", "picturePC"]
var storesImages:[String] = ["newarkStore", "compeStore"]
override func viewDidLoad() {
super.viewDidLoad()
searchQ.delegate = self
scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
// searchQ.delegate = self
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//self.tabBarController?.tabBar.isHidden = false
}
func searchBarSearchButtonClicked(_ searchQ: UISearchBar)
{
print("*********")
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let resultViewController = storyBoard.instantiateViewController(withIdentifier: "SearchViewController") as! searchViewController
// resultViewController.searchQuery = searchQ
resultViewController.doAthing(searchQ)
self.tabBarController?.selectedIndex = 3
// NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
resultViewController.resultsView.reloadData()
}
// func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
//
// print("*********")
//
// let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "SearchViewController") as! searchViewController
// secondViewController.doAthing(searchBar)
// self.navigationController!.pushViewController(secondViewController, animated: true)
//
//
//
// //let titles: Elements = try doc.select("a[product-thumb]")
// //let titles: String = try doc.select("a").attr("product-thumb")
//
// // print(titles
//
// hideKeyboardWhenTappedAround()
//
// }
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
searchViewController.swift
import UIKit
import SwiftSoup
class searchViewController: UIViewController{
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var resultsView: UITableView!
#IBOutlet var searchQuery: UISearchBar!
// let content = try! String(contentsOf: URL(string: "https://www.locally.com/search/all/activities/depts?q=bottle")!)
// let doc: Document = try! SwiftSoup.parse(content)
var products: [String] = []
//let products = ["Computer", "PC", "Laptop"]
let stores = ["Computer Central", "Fry's Electronics", "Best Buy"]
//var stores: [String] = []
let into = ["Custom PC with high performanc. Perfect for gaming and streaming. Great condition", "Custom PC with high performanc. Perfect for gaming and streaming. Great condition", "Custom PC with high performanc. Perfect for gaming and streaming. Great condition"]
var dollars: [String] = []
//let dollars = [100, 220, 129, 100, 220, 129]
let likes = [10, 24, 24, 24 , 456, 46, 46]
//let miles = ["3.2 mi", "4.1 mi", "6.3 mi", "3.2 mi", "4.1 mi", "6.3 mi"]
var miles: [String] = []
// let names = ["Central Computers", "CompE", "geekStore", "Central Computers", "CompE", "geekStore"]
var names: [String] = []
let numbers = ["510-329-0172", "510-456-7345", "510-329-0172", "510-329-0172", "510-456-7345", "510-329-0172"]
var images: [UIImage] = []
var categories: [String] = []
var descriptions: [String] = []
var stars: [String] = []
var ratings: [Double] = []
var ratingImage: [[UIImage]] = [[]]
var phones: [String] = []
var cities: [String] = []
//var webCounter:Int = 0
var x:Double = 0
var y:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
searchQuery.delegate = self
resultsView.delegate = self
resultsView.dataSource = self
//set the height of each row in tableview
self.resultsView.rowHeight = 200.0
// Do any additional setup after loading the view.
}
#objc func loadList(notification: NSNotification) {
//load data here
self.resultsView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
//searchBar.inputViewController?.dismissKeyboard()
searchBar.inputViewController?.dismiss(animated: true)
doAthing(searchBar)
self.searchQuery.endEditing(true)
self.resultsView.keyboardDismissMode = .onDrag
hideKeyboardWhenTappedAround()
}
func doAthing(_ searchBar: UISearchBar) {
do {
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.medium
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
print("reached here")
let html = try String(contentsOf: URL(string: "https://www.locally.com/search/all/activities/depts?q=" + searchBar.text!)!)
//let doc: Document = try SwiftSoup.parse(html)
guard let titles: Elements = try? SwiftSoup.parse(html).getElementsByClass("product-thumb ") else {return}//select("a") else {return}
guard let prices: Elements = try? SwiftSoup.parse(html).getElementsByClass("product-thumb-price dl-price") else {return}
guard let Stores: Elements = try? SwiftSoup.parse(html).getElementsByClass("filter-label-link") else {return}
guard let Images: Elements = try? SwiftSoup.parse(html).getElementsByClass("product-thumb-img") else {return}
products = []
miles = []
dollars = []
names = []
images = []
stars = []
for title: Element in titles.array() {
print("title" + String(titles.size()))
products.append(try! title.attr("data-product-name"))
guard let url: URL = try? URL(string: "https://www.locally.com/" + String(try! title.attr("href")))
else
{
miles.append("-")
continue
}
let html1 = try String(contentsOf: url)
guard let distances: Element = try? SwiftSoup.parse(html1).getElementsByClass("conv-section-distance dl-store-distance").first()
else
{
miles.append("-")
continue
}
print(try! distances.ownText())
miles.append(try! distances.ownText())
guard let K: Array<String> = try? SwiftSoup.parse(html1).getElementsByClass("breadcrumbs container").eachText() else {return}
let str = (try! K.last!.components(separatedBy: "/") )
categories.append(str[str.count - 2])
print(str[str.count - 2])
guard let desc: Array<String> = try? SwiftSoup.parse(html1).getElementsByClass("pdp-information").eachText() else {return}
guard let s:String = try? desc[1]
else {
return
}
descriptions.append( String( s.suffix(s.count - 19) ) )
print( String( s.suffix(s.count - 19) ) )
guard let star: Element = try? SwiftSoup.parse(html1).getElementsByClass("stars").first()
else
{
print("no reviews")
ratings.append(0)
continue
}
print(try! star.attr("data-rating"))
//ratings.append(try! star.attr("data-rating")) ?? ()
let x = Double(try! star.attr("data-rating")) ?? 0
print("y: " + String(Int(x)))
ratings.append(x)
guard let locations: Element = try? SwiftSoup.parse(html1).getElementsByClass("conv-section-store-address section-subtitle dl-store-address js-store-location").first()
else
{
print("cant find city")
return
}
let string = locations.ownText()
print("location: " + String(string.prefix(string.count - 10)) )
cities.append(try! String(string.prefix(string.count - 10)))
guard let phoneNums: Element = try? SwiftSoup.parse(html1).getElementsByClass("selected-retailer-info-link btn-action-sm tooltip").first()
else
{
print("link not found")
return
}
guard let urls:URL = try? URL(string: "https://www.locally.com/" + phoneNums.attr("href") )
else
{
return
}
let html2 = try String(contentsOf: urls )
guard let storePage:Element = try? SwiftSoup.parse(html2).getElementsByClass("landing-page-phone-label").first() else {
print("Phone Number not found")
return
}
let sp = try? storePage.ownText
if let s = sp {
phones.append(try! s())
}
else {
phones.append("N/A")
}
print(try! storePage.ownText())
//
// let html1 = try String(contentsOf: url)
}
for price: Element in prices.array() {
print("prices" + String(prices.size()))
print(String(try! price.ownText()))
dollars.append(try! price.ownText())
}
for store: Element in Stores.array() {
print("Stores" + String(Stores.size()))
names.append(try! store.ownText()) ?? names.append("N/A")
}
for image: Element in Images.array() {
guard let url = URL(string: try! image.attr("src") ) else { return }
let data = try? Data(contentsOf: url)
if let imageData = data {
images.append( UIImage(data: imageData)! )
}
else {
images.append(UIImage(named: "pcPic")!)
}
//images.append(try! image.downloaded(from: image.attr("src")))
}
dismiss(animated: false, completion: nil)
resultsView.reloadData()
//let titles: Elements = try doc.select("a[product-thumb]")
//let titles: String = try doc.select("a").attr("product-thumb")
// print(titles)
} catch Exception.Error(type: let type, Message: let message) {
print(type)
print(message)
} catch {
print("")
}
}
// override func viewWillAppear(_ animated: Bool) {
// super.viewWillAppear(animated)
// self.tabBarController?.tabBar.isHidden = false
// }
//When user changes segment, tableview is reloaded
#IBAction func segmentChanged(_ sender: Any) {
resultsView.reloadData();
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
func imageWithImage(image: UIImage, scaledToSize newSize: CGSize) -> UIImage {
UIGraphicsBeginImageContext(newSize)
image.draw(in: CGRect(x: 0 ,y: 20 ,width: newSize.width ,height: newSize.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!.withRenderingMode(.alwaysOriginal)
}
}
extension searchViewController: UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("reached here")
switch segmentedControl.selectedSegmentIndex {
case 0:
return products.count
case 1:
return stores.count
case 2:
return (products.count + stores.count)
default:
break
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "resultsTableViewCell") as! resultsTableViewCell
switch segmentedControl.selectedSegmentIndex {
case 0:
cell.titleLabel?.text = products[indexPath.row]
cell.productImage?.image = images[indexPath.row]
// cell.descriptionLabel?.text = into[indexPath.row]
// cell.descriptionLabel?.numberOfLines = 0
cell.price?.text = String(dollars[indexPath.row])
cell.distance?.text = miles[indexPath.row]
cell.storeName?.numberOfLines = 0
cell.storeName?.text = names[indexPath.row]
cell.phoneNumber?.text = phones[indexPath.row]
let ratNumber = ratings[indexPath.row]
if( ratNumber == 0 )
{
cell.rating?.text = "No Reviews"
}
else
{
cell.rating?.text = String(ratNumber)
}
if(ratNumber > 4.5)
{
cell.star1.image = UIImage(named: "regular_5")
}
else if(ratNumber > 4.0)
{
cell.star1.image = UIImage(named: "regular_4_half")
}
else if(ratNumber == 4.0)
{
cell.star1.image = UIImage(named: "regular_4")
}
else if(ratNumber > 3.0)
{
cell.star1.image = UIImage(named: "regular_3_half")
}
else if(ratNumber == 3.0)
{
cell.star1.image = UIImage(named: "regular_3")
}
else if(ratNumber > 2.0)
{
cell.star1.image = UIImage(named: "regular_2_half")
}
else if(ratNumber == 2.0)
{
cell.star1.image = UIImage(named: "regular_2")
}
else if(ratNumber > 1.0)
{
cell.star1.image = UIImage(named: "regular_1_half")
}
else if(ratNumber == 1.0)
{
cell.star1.image = UIImage(named: "regular_1")
}
else
{
cell.star1.image = UIImage(named: "regular_0")
}
cell.rating?.numberOfLines = 0
// cell.phoneNumber?.text = numbers[indexPath.row]
case 1:
cell.titleLabel?.text = stores[indexPath.row]
cell.productImage?.image = imageWithImage(image: UIImage.init(named: "pcPic")!, scaledToSize: CGSize(width: 400, height: 300))
// cell.descriptionLabel?.text = into[indexPath.row]
// cell.descriptionLabel?.numberOfLines = 0
cell.price?.text = String(dollars[indexPath.row])
cell.distance?.text = miles[indexPath.row]
cell.storeName?.text = names[indexPath.row]
cell.phoneNumber?.text = numbers[indexPath.row]
case 2:
var all = products + stores
all.shuffle()
let alls = into + into
cell.titleLabel?.text = all[indexPath.row]
cell.productImage?.image = imageWithImage(image: UIImage.init(named: "pcPic")!, scaledToSize: CGSize(width: 400, height: 300))
// cell.descriptionLabel?.text = alls[indexPath.row]
// cell.descriptionLabel?.numberOfLines = 0
cell.price?.text = String(dollars[indexPath.row])
cell.distance?.text = miles[indexPath.row]
cell.storeName?.text = names[indexPath.row]
cell.phoneNumber?.text = numbers[indexPath.row]
default:
break
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// let vc = self.storyboard?.instantiateViewController(withIdentifier: "ShopViewController") as! ShopViewController
// self.present(vc, animated: true, completion: nil)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationVC = storyboard.instantiateViewController(withIdentifier: "ProductViewController") as! ProductViewController
destinationVC.ktitle = products[indexPath.row]
destinationVC.kprice = dollars[indexPath.row]
// destinationVC.kdescription = [indexPath.row]
destinationVC.kimage = images[indexPath.row]
// destinationVC.klikes = likes[indexPath.row]
destinationVC.kcategory = categories[indexPath.row]
destinationVC.kdescription = descriptions[indexPath.row]
destinationVC.kname = names[indexPath.row]
destinationVC.kmiles = miles[indexPath.row]
destinationVC.klocation = cities[indexPath.row]
self.present(destinationVC, animated: true, completion: nil)
}
}
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard(_:)))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard(_ sender: UITapGestureRecognizer) {
view.endEditing(true)
if let nav = self.navigationController {
nav.view.endEditing(true)
}
}
}
First, it's probably not a good idea to switch to a different tab in that manner. Users understand that tapping a tab-bar icon/button takes them to a new tab. If you're jumping around in the tabs via code, it can be very confusing for the user.
But... it's your app.
The reason your code does not "refresh my tableView" is because you never actually reference that view controller.
In your code:
func searchBarSearchButtonClicked(_ searchQ: UISearchBar)
{
print("*********")
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
// here you create a NEW instance of searchViewController
let resultViewController = storyBoard.instantiateViewController(withIdentifier: "SearchViewController") as! searchViewController
// you call a func in that instance
resultViewController.doAthing(searchQ)
// you switch to tab index 3, which holds an OLD instance of searchViewController
self.tabBarController?.selectedIndex = 3
// you tell resultsView in the NEW instance to reloadData()
resultViewController.resultsView.reloadData()
// as soon as you exit this func, the NEW instance is deleted
}
You could do something like this:
func searchBarSearchButtonClicked(_ searchQ: UISearchBar)
print("*********")
// get a reference to the searchViewController that is already
// part of the tab bar controller
if let tb = self.tabBarController,
let controllers = tb.viewControllers,
let resultVC = controllers[3] as? searchViewController {
// call the func
resultVC.doAthing("new")
tb.selectedIndex = 3
}
}
However, that's really not a great approach... Just for one example, Suppose you change the order of the tabs at some point?
You're better off using either a custom tab bar controller class, or using a "data manager" class.
I'd suggest you head over to google (or your favorite search engine) and search for swift pass data between tab bar view controllers -- you'll find plenty of discussions, articles, examples, etc.
Edit -- comment
In HomeViewController replace your func searchBarSearchButtonClicked with this:
func searchBarSearchButtonClicked(_ searchQ: UISearchBar)
{
print("1 - searchBarSearchButtonClicked")
guard let btnTitle = searchQ.largeContentTitle else {
print("2 - FAILED to get searchQ.largeContentTitle")
return
}
print("2 - got btnTitle", btnTitle)
if let tb = self.tabBarController {
print("3 - got a tab bar controller reference")
if let controllers = tb.viewControllers {
print("4 - got controllers reference")
if controllers.count == 4 {
print("5 - we have 4 controllers")
if let resultVC = controllers[3] as? searchViewController {
print("6 - got a reference to searchViewController")
// if we have not yet selected the 4th tab,
// the view will not yet have been loaded
// so make sure it is
resultVC.loadViewIfNeeded()
// call the func, passing the button title
resultVC.doAthing(searchQ)
// switch to the 4th tab
tb.selectedIndex = 3
print("7 - we should now be at searchViewController")
} else {
print("6 - FAILED TO GET a reference to searchViewController")
}
} else {
print("5 - FAILED TO GET a reference to searchViewController")
}
} else {
print("4 - FAILED TO GET a reference to searchViewController")
}
} else {
print("3 - FAILED TO GET a reference to searchViewController")
}
}
Then see what messages get printed to the debug console.
I am getting this error when trying to save a string to coreData inside my newJobNote class. It seems to crash in the function private func createNote() on the line let tuple = CoreDataManager.shared.createJobNote(jobNote: jobNote, job: job)
This is the error:
2018-05-03 17:37:53.180012+0100 Shoot[7022:3228759] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "note"; desired type = NSString; given type = JobNote; value = (entity: JobNote; id: 0x1c002f080 ; data: {
job = "0xd000000000580000 ";
note = nil;
}).'
I have an entity named Job that stores a job name - I am then passing this through to my JobNoteVC.
I also have an entity named: JobNote with an attribute named: note. I have also made a structure for my core data:
func createJobNote(jobNote: String, job: Job) -> (JobNote?, Error?) {
let context = persistentContainer.viewContext
// create note
let jobNote = NSEntityDescription.insertNewObject(forEntityName: "JobNote", into: context) as! JobNote
jobNote.job = job
jobNote.setValue(jobNote, forKey: "note")
do {
try context.save()
return (jobNote, nil)
} catch let error {
print ("Failed to add camera:", error)
return (nil, error)
}
}
I then have a note page that contains a tableView that will display all notes that are stored in coredata:
import UIKit
import CoreData
class notes : UIViewController, UITableViewDelegate, UITableViewDataSource, NewJobNoteControllerDelegate {
let cellId = "cellId"
var tableView = UITableView()
var jobNotesArray = [JobNote]()
var job: Job? {
let tabBarController = self.tabBarController as! jobTabController
return tabBarController.job
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.navigationItem.title = "NOTES"
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(HandleNewNote))
self.tabBarController?.navigationItem.rightBarButtonItems = [addButton]
addTableView()
fetchJobNotes()
}
func addTableView() {
// TABLE VIEW SETUP
tableView.frame = self.view.frame
self.view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
}
private func fetchJobNotes(){
guard let jobNotes = job?.jobNotes?.allObjects as? [JobNote] else { return }
self.jobNotesArray = jobNotes
}
func didAddJobNote(jobNote: JobNote) {
jobNotesArray.append(jobNote)
tableView.reloadData()
}
func didEditJobNote(jobNote: JobNote) {
let row = jobNotesArray.index(of: jobNote)
let reloadIndexPath = IndexPath(row: row!, section: 0)
tableView.reloadRows(at: [reloadIndexPath], with: .middle)
}
#IBAction func HandleNewNote(sender : UIButton) {
let newNote = newJobNote()
newNote.delegate = self
newNote.job = job
let navController = UINavigationController(rootViewController: newNote)
present(navController, animated: true, completion: nil)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jobNotesArray.count
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
return cell
}
}
This is my newNoteVC:
import CoreData
protocol NewJobNoteControllerDelegate {
func didAddJobNote(jobNote : JobNote)
func didEditJobNote(jobNote: JobNote)
}
class newJobNote: UIViewController {
var delegate: NewJobNoteControllerDelegate?
var job: Job?
let noteLabel: UILabel = {
let label = UILabel()
label.text = "NOTE"
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor(red:0.63, green:0.63, blue:0.63, alpha:1.0)
return label
}()
var noteTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Enter Note..."
textField.tintColor = UIColor(red:0.87, green:0.30, blue:0.32, alpha:1.0)
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
var jobNote : JobNote? {
didSet {
noteTextField.text = jobNote?.note
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
let backButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(HandleBack))
navigationItem.leftBarButtonItem = backButton
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationItem.title = "NEW NOTE"
}
private func createNote() {
guard let jobNote = noteTextField.text else { return }
guard let job = self.job else { return }
let tuple = CoreDataManager.shared.createJobNote(jobNote: jobNote, job: job)
if let error = tuple.1 {
print(error)
} else {
dismiss(animated: true, completion: {
self.delegate?.didAddJobNote(jobNote: tuple.0!)
})
}
}
private func saveNoteChanges() {
let context = CoreDataManager.shared.persistentContainer.viewContext
jobNote?.note = noteTextField.text
do {
try context.save()
dismiss(animated: true) {
self.delegate?.didEditJobNote(jobNote: self.jobNote!)
}
} catch let error {
print ("Failed to save camera changes:", error)
}
}
#IBAction private func HandleBack(sender : UIButton) {
if jobNote == nil {
createNote()
print("back to note and saved")
} else {
saveNoteChanges()
print("back to note and saved")
}
}
}
The problem is here
jobNote.setValue(jobNote, forKey: "note")
you can't set entity instance to an attribute inside the entity itself , change parameter name
func createJobNote(sendedNote: String, job: Job) -> (JobNote?, Error?) {
let context = persistentContainer.viewContext
// create note
let jobNote = NSEntityDescription.insertNewObject(forEntityName: "JobNote", into: context) as! JobNote
jobNote.job = job
jobNote.setValue(sendedNote, forKey: "note")
do {
try context.save()
return (jobNote, nil)
} catch let error {
print ("Failed to add camera:", error)
return (nil, error)
}
}
This is my first post, so I am open to feedback if I can improve my question.
I have been studying Swift for about a year now and I am trying to build an app that uses UIPageViewController to make an app that mimics the experience of a book. The user is able to create as many pages as they want. For now, I have a template UIViewController with an imageView and a textView. The user presses a "+" button to add the next page, which should appear as a pre-set blank imageView (that they can set) and a blank textView.
Specs:
I'm persisting user information with CoreData.
The problem:
I create a new set of pages. Then I add an image and some text. I tap "+" and do the same on the next page. When I tap "+" after that, the next page and each page after that copies the image and text from the previous page rather than being an empty page.
Here is the logic for how my pages should be run.
extension BookPageViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = storyPageViewControllers.index(of: viewController as! TemplateViewController) else { return nil }
if viewControllerIndex == 0 { return nil } // Page won't scroll below first page.
let previousIndex = viewControllerIndex - 1
guard storyPageViewControllers.count > previousIndex else { return nil }
currentPage = Double(previousIndex)
return storyPageViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = storyPageViewControllers.index(of: viewController as! TemplateViewController) else { return nil }
let nextIndex = viewControllerIndex + 1
let storyPageViewControllersCount = storyPageViewControllers.count
guard storyPageViewControllersCount != nextIndex else { return nil }
guard storyPageViewControllersCount > nextIndex else { return nil }
currentPage = Double(nextIndex)
return storyPageViewControllers[nextIndex]
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return storyPageViewControllers.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first,
let firstViewControllerIndex = storyPageViewControllers.index(of: firstViewController as! TemplateViewController) else
{ return 0 }
return firstViewControllerIndex
}
}
This is the IBAction for "+" button:
guard let book = book else { return }
PageController.shared.createPageWith(text: "", pageNumber: currentPage + 1.0, image: #imageLiteral(resourceName: "ImageViewPlaceHolder"), book: book)
updateStoryPageViewControllers()
setViewControllers([storyPageViewControllers[Int(currentPage) + 1]], direction: .forward, animated: true, completion: nil)
UPDATE:
As requested, here are the functions commenters asked for.
func updateStoryPageViewControllers() {
// Mark: - RETURN HERE TO TALK TO SPENCER ABOUT FIXING THE VC UPDATING.
// Make a check to see which pages already have view controllers, and only make a new view controller for the pages that don't.
guard let book = book else { return }
// Create an array of view controllers to store the page VCs being instantiated
var temporaryViewControllers: [TemplateViewController] = []
// Loop through the books pages, and make a view controller for each
if let pages = book.pages?.array as? [Page], pages.count > 0 {
for page in pages {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let pageVC = storyboard.instantiateViewController(withIdentifier: "TemplateViewController") as? TemplateViewController else { break }
pageVC.page = page
temporaryViewControllers.append(pageVC)
}
} else {
//FIXME: - Default image set here.
guard let page = PageController.shared.createPageWith(text: "", pageNumber: 0, image: #imageLiteral(resourceName: "ImageViewPlaceHolder"), book: book) else { return }
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let pageVC = storyboard.instantiateViewController(withIdentifier: "TemplateViewController") as? TemplateViewController else { return }
pageVC.page = page
temporaryViewControllers.append(pageVC)
}
// Set the self.storyPageViewControllers array to the array you just made and filled.
self.storyPageViewControllers = temporaryViewControllers
}
Here is the code for the createPageWith method:
#discardableResult func createPageWith(text: String, pageNumber: Double, image: UIImage, book: Book) -> Page? {
//Turns the UIImage into data to be saved in CoreData.
guard let data = UIImageJPEGRepresentation(image, 0.8) else { return nil }
let page = Page(text: text, pageNumber: pageNumber, photoData: data, book: book)
BookController.shared.saveToPersistentStore()
return page
}
I was following this great tutorial from Duc Tran about UIPageViewController. I was wondering how would you make the controllers inside your UIPageViewController transition automatically without having the user swipe. This is how the code looks without the delegate and datasource.
class AnimesPageVC: UIPageViewController {
override var navigationOrientation: UIPageViewControllerNavigationOrientation {return .horizontal}
weak var pageViewControllerDelegate: AnimePagesVCDelegate?
var timeInterval: Int?
var images: [UIImage]?
lazy var animes: [UIViewController] = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var animes = [UIViewController]()
if let images = self.images {
for image in images {
let anime = storyboard.instantiateViewController(withIdentifier: "AnimeImage")
animes.append(anime)
}
}
self.pageViewControllerDelegate?.setUpPageController(numberOfPages: animes.count)
return animes
}()
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
dataSource = self
loadPage(atIndex: 0)
}
func loadPage(atIndex: Int) {
let anime = animes[atIndex]
var direction = UIPageViewControllerNavigationDirection.forward
if let currentAnime = viewControllers?.first {
if let currentIndex = animes.index(of: currentAnime) {
if currentIndex > atIndex {
direction = .reverse
}
}
}
configurePages(viewController: anime)
setViewControllers([anime], direction: direction, animated: true, completion: nil)
}
func configurePages(viewController: UIViewController) {
for (index, animeVC) in animes.enumerated() {
if viewController === animeVC {
if let anime = viewController as? AnimeVC {
anime.image = self.images?[index]
self.pageViewControllerDelegate?.turnPageController(to: index)
}
}
}
}
}
So how would I be able to get that kind of behavior. Would appreciate any help. :)
Add a timer to your view did load and call the same load function with updated index
Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { (_) in
// call your function here
self.loadPage(atIndex: index + 1)
// you have to update your index also.
self.index = self.index + 1
}
This will call your loadPage function each 0.3 sec
keep in mind that my solution is only for one way if you it's only going to next page because I am adding to automatically it will not come back to previous controller for that you have do something like
index = index - 1
I have MainViewController that is my UIPageViewController. Ther are 2 views FirstDataViewController and SecondDataViewController that i want to show.
I want to execute my request that updates items in MainViewControllre and then pass it to my 2 child views.
getData is my function that contains data from Request and then updates MainViewController's items
extension MainViewController {
func getData() {
getBasicData() { [weak self] (basicModel) in
guard let strongSelf = self else { return }
strongSelf.getExperienceData() { [weak self] (experienceModel) in
guard let strongSelf = self else { return }
let items = RequestItems(basicData: basicModel,
experienceData: experienceModel)
strongSelf.updateItems(items: items)
}
}
}
}
MainViewController:
class MainViewController: UIPageViewController {
var items: RequestItems
let firstDataViewController: FirstDataViewController
let secondDataViewController: SecondDataViewController
let basicDataManager: APIManagerProtocol
let experienceDataManager: APIManagerProtocol
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.firstDataViewController, self.secondDataViewController]
}()
convenience init() {
self.init(with: RequestItems())
}
init(with items: RequestItems) {
self.items = items
let apiManager = APIManager()
basicDataManager = apiManager.createBasicDataManager()
experienceDataManager = apiManager.createExperienceDataManager()
self.firstDataViewController = FirstDataViewController(with: items)
self.secondDataViewController = SecondDataViewController(with: items)
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
self.edgesForExtendedLayout = []
}
func updateItems(items: RequestItems) {
self.items = items
}
}
How to use getData() function to update MainViewController items first and then pass data to child views? Or maybe there is just better option to do this?
It's not a good solution. "GetData" method needs some time to execute completely so you should:
Execute "getData" before load MainViewController, then pass "request items" to it and update yours views.
But I assume that your MainViewController is first controller in your application so you should:
Add completion block to your "getData" method:
func getData(_ completion: [RequestItems] -> ()) {
getBasicData() { [weak self] (basicModel) in
guard let strongSelf = self else { return }
strongSelf.getExperienceData() { [weak self] (experienceModel) in
guard let strongSelf = self else { return }
let items = RequestItems(basicData: basicModel,
experienceData: experienceModel)
completion(items)
}
}
}
Add "updateItems" method to yours view controllers (instead of passing it in init method)
Call your "getData" method with completion handler but outside of MainViewController init
init(with items: RequestItems) {
...
self.items = items
self.firstDataViewController = FirstDataViewController()
self.secondDataViewController = SecondDataViewController()
...
}
func viewDidLoad() {
super.viewDidLoad()
getBasicData { [weak self] (items) in
guard let strongSelf = self else { return }
strongSelf.updateItems(items)
strongSelf.firstDataViewController.updateItems(with: items)
strongSelf.secondDataViewController.updateItems(with: items)
}
}
func updateItems(items: RequestItems) {
self.items = items
}