I have an ios app. I need to store two fields in my database realm(Title and image(or path to image). Now my data is storing in struct video but after reloading the app all changes missed. So how can I implement realm in my app?
My code looks :
struct Video {
var image: UIImage
var title : String }
class VideoCell: UITableViewCell {
var videoImageView=UIImageView()
var videoTitleLabel=UILabel()
}
required init?(coder:NSCoder){
fatalError("init(coder :) has not been implemented")
}
func set(video : Video) {
videoTitleLabel.text = video.title
videoImageView.image = video.image
}
class VideoListVC: UIViewController, UISearchResultsUpdating {
var tableView=UITableView()
var videos : [Video] = []
var videoImageView=UIImageView()
var videoTitleLabel=UILabel()
struct Cells {
static let videoCell = "VideoCell"
}
override func viewDidLoad(){
super.viewDidLoad()
title = ""
videos = fetchData()
configureTableView()
}
func configureTableView(){
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 290
tableView.register(VideoCell.self, forCellReuseIdentifier: Cells.videoCell)
tableView.pin(to:view)
} func setTableViewDelegates(){
tableView.delegate=self
tableView.dataSource=self
}
extension VideoListVC: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.videoCell) as! VideoCell
let video = videos[indexPath.row]
cell.set(video: video)
return cell
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.beginUpdates()
videos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}
}
import realm-swift from github here https://github.com/realm/realm-swift
your data should looks somewhat similar to this
class Video: Object {
#Persisted(primaryKey: true) var _id: String
#Persisted var image: Data
#Persisted var title: String
}
I am assuming you want to persist your data after you fetch.
do {
let realm = try Realm()
try realm.write {
realm.add(video)
}
} catch {
assertionFailure("\(error)")
}
Not sure if there is a way to persist UIImage. I usually convert it to Data when I persist and then initialize the image using the Data when I retrieve.
Related
my cells are not appearing.
I did:
Checked if datasource and delegate were connected
Checked if my custom cells identifier name and class were correct
Things that I didn't:
I am struggling with auto layout, so I just decided not to do it.
My app is loading with the correct amount of cells, but the cells are not registered.
My code:
import UIKit
class WelcomeViewController: UITableViewController, NetworkManagerDelegate {
private var networkManager = NetworkManager()
private var infoForCells = [Result]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "ImageViewCell", bundle: nil), forCellReuseIdentifier: "imageCell")
networkManager.delegate = self
networkManager.fetchNews()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoForCells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? ImageViewCell else{
return UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let cellIndex = infoForCells[indexPath.row]
cell.titleForImage.text = cellIndex.alt_description
print(cell.titleForImage ?? "lol")
// if let image = cellIndex.urlToImage {
// cell.imageForArticle.load(url: image)
// }
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func didUpdateNews(root: Root) {
infoForCells = root.results
}
}
Reload the table
func didUpdateNews(root: Root) {
infoForCells = root.results
tableView.reloadData()
}
In addition to Sh_Khan answer you can also listen to updates of infoForCells property
private var infoForCells = [Result]() {
didSet {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
}
I tried, expandable drop down inside tableview in swift4 but not worked my below code. if i changed custom model class to String it is working. my requirement use below custom model class. please check my below code.
class ExpandViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, ExpandableHeaderViewDelegate {
var empDetails = [EmpWorking]()
var hiredDetails = [Hired]()
#IBOutlet weak var tableView: UITableView!
var sections = [SectionData]()
override func viewDidLoad() {
super.viewDidLoad()
loadData()
// Do any additional setup after loading the view.
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].emp.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if(sections[indexPath.section].expanded){
return 80
}
else{
return 0
}
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 2
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
var header = ExpandableHeaderView()
header.customInit(title: sections[section].genre, section: section, delegate: self)
return header
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "labelCell", for: indexPath) as! expandDataCell
cell.lbl1.text = "\(sections[indexPath.section].emp![indexPath.item].name)"
cell.lbl2.text = "\(sections[indexPath.section].emp![indexPath.item].dept)"
return cell as expandDataCell
}
func toggleSection(header: ExpandableHeaderView, section: Int) {
sections[section].expanded = !sections[section].expanded
tableView.beginUpdates()
for i in 0 ..< sections[section].emp.count
{
tableView.reloadRows(at: [IndexPath(row: i, section: section)], with: .automatic)
}
tableView.endUpdates()
}
func loadData(){
let url = "http://www.json-generator.com/api/json/get/ccgFfBFWqa?indent=2"
guard let resourceString = URL(string: url) else{return}
let decoder = JSONDecoder()
URLSession.shared.dataTask(with: resourceString) { (data, response, err) in
guard let data = data else{return}
do{
let expandData = try decoder.decode([ExpandModel].self, from: data)
print("Expand Data:\(expandData)")
for i in expandData[0].emp_working{
print("i=\(i)")
self.empDetails.append(i)
}
for j in expandData[0].emp_recent_hired{
self.hiredDetails.append(j)
}
DispatchQueue.main.async {
// self.sections = [SectionData(genre: "Employee Working", emp: self.empDetails, expanded: false),
// SectionData(genre: "Employee Hired", emp: self.hiredDetails, expanded: false)
// ]
self.tableView.reloadData()
}
}
catch let jsonErr{
print("Expand Data Err:\(jsonErr)")
}
}.resume()
}
}
struct ExpandModel: Codable{
let emp_working: [EmpWorking]
let emp_recent_hired: [Hired]
}
struct EmpWorking: Codable{
let dept: String
let name: String
}
struct Hired: Codable {
let name: String
let qualification: String
}
struct SectionData {
var genre: String!
var emp: [EmpWorking]!
var expanded: Bool!
init(genre: String, emp: [EmpWorking] = [], expanded: Bool)
{
self.genre = genre
self.emp = emp
self.expanded = expanded
}
}
Required output:
Employee Working >
Employee Hired >
if click on Employee Working, then it will display the below output:
Employee Working V
John
Web
Alex
Mobile
storyBoard design
in above image you can see there are two label where dynamic data will appear. On every cell index click it will expand the cell as you see in below screen.
simple view appear like this
After expend a cell it will appear like this
so all you need to do these steps:-
first create a variable for store the didselectrowat index for ex:-
var int_row = Int()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
int_row = indexPath.row . // save the index path
yourTableViewName.reloadData() // reload the table
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
if int_row == indexPath.row { // when you tap on cell then it will expend
the cell
return UITableView.automaticDimension
} else {
return 48 // at this time only show your uppar labal only
}
}
I can't seem to understand how a JSON array be edited when presented in a UITableView?
The JSON array looks like the following example:
[
{
"customer": "John",
"status": "Yes",
},
{
"customer": "James",
"status": "No",
},
{
"customer": "Jamie",
"status": "No",
}
]
The way the app currently presents the data in the UITableView:
private func fetchJSON() {
guard let url = URL(string: "https://example/example/example"),
let value = driver.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "driver=\(value)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([Structure].self,from:data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
}.resume()
}
TableView Delegate code so far:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return pickup.count
}
print(structure.count)
return structure.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testingCell", for: indexPath)
let portfolio: Structure
portfolio = structure[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
I would like to be able to select the customers names James and Jamie and press a button that will change the status from No to Yes.
How can the selection and update mechanism be created from the apps perspective?
UPDATE:
structure is defined as: var structure = [Structure]()
import UIKit
struct Structure: Codable {
let customer: String
let status: String
}
UPDATE 2:
I am working with the following but I cannot seem to update the API:
func saveToDB(_ structure: Structure) {
do{
let data = try JSONEncoder().encode(structure)
if let str = String(data: data, encoding: .utf8) {
print(str)
guard let url = URL(string: "https://example/example/example"),
else { return }
}
} catch {
print(error)
}
}
You must change the status in structure array w.r.t the selected indexPath and not in the JSON directly.
In tableView(_: didSelectRowAt:) and tableView(_: didDeselectRowAt:), you can simply change the status value of structure at that particular indexPath like so,
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
structure[indexPath.row].status = "Yes"
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
structure[indexPath.row].status = "No"
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
In case the issue is not resolved, do add the definition of Structure along with more details, so I can help you further.
Edit:
First of all, the definition of Structure goes like,
class Structure: Codable {
var customer: String
var status: String
}
The code to update the status of structure an indexPath is:
class VC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var structures = [Structure]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelection = true
fetchJSON()
}
private func fetchJSON() {
//code to fetch data from JSON...
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return structures.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testingCell", for: indexPath)
cell.textLabel?.text = structures[indexPath.row].customer
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let structure = structures[indexPath.row]
structure.status = "Yes"
saveToDB(structure)
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let structure = structures[indexPath.row]
structure.status = "No"
saveToDB(structure)
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
func saveToDB(_ structure: Structure) {
//add the code to save structure in DB...
}
}
Now, when saving to the DB, there is no need for the JSON response. You can directly save the structure instance in the DB.
Edit-2:
Update the saveToDB(_:) method to:
func saveToDB(_ structure: Structure) {
do{
let data = try JSONEncoder().encode(structure)
if let str = String(data: data, encoding: .utf8) {
print(str)
//str is the updated Structure that you can send in your API...
}
} catch {
print(error)
}
}
In the above method, you'll get the JSON format of the updated structure instance that you can use to call your API.
Hope this will resolve your problem
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testingCell", for: indexPath)
let portfolio: Structure = structure[indexPath.row]
cell.accessoryType = portfolio.status == "YES" ? .checkmark : .none
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let status = structure[indexPath.row].status // get current status
structure[indexPath.row].status = status == "YES" ? "NO" : "YES" /// toggle status
tableView.reloadData()
}
No need didDeselectRowAt method.
I'm developing an application that has a "Plus" button which can add stopwatch to a table view, every cell has its own timer, and can be played by itself.
When I'm trying to delete one cell like that, random issues are happening like:
Order of the stopwatches being changed
some stopwatches time is being zeroed .
If trying to add new stopwatch after, an old stopwatch with it's timer are back!
TableView
class StopWatchViewController: UIViewController {
#IBOutlet weak var stopWatchesTableView: UITableView!
var stopwatchesList: [String] = []
var stopwatchesNum : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
stopWatchesTableView.delegate = self
stopWatchesTableView.dataSource = self
NotificationCenter.default.addObserver(self,
selector: #selector(applicationDidEnterBackground(noti:)),
name: UIApplication.didEnterBackgroundNotification,
object: nil)
}
#objc func applicationDidEnterBackground(noti: Notification) {
// Save Date
let shared = UserDefaults.standard
shared.set(Date(), forKey: "SavedTime")
print(Date())
}
func refresh() {
stopWatchesTableView.reloadData()
}
#IBAction func AddStopWatch(_ sender: Any) {
stopwatchesNum += 1;
stopwatchesList.append(String(format: "Stopwatch %d", stopwatchesNum))
refresh()
}
}
extension StopWatchViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stopwatchesList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let stopWatch = stopwatchesList[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "StopwatchCell") as! StopWatchCell
cell.initCell(title: stopWatch, index: indexPath.row)
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
stopwatchesList.remove(at: indexPath.row)
stopWatchesTableView.deleteRows(at: [indexPath], with: .automatic)
refresh()
}
}
}
What can cause such issues ?
Don't call refresh method after deleting the row.Hope this help.
Im working on a project in swift 3.0, and i'm populating data on a table view, which I save on a core data entity from another view controller (from two text fields). I wants to delete data when swipe to delete is activated both from my array and core data. my code on UITableView class as bellow.
import Foundation
import UIKit
import CoreData
class MyExpencesViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var expensesTableView: UITableView!
var myExpensesArray = [String] ()
var myAmountArray = [String] ()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadData()
self.expensesTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (myExpensesArray.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: MyExpensesTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MyExpensesTableViewCell
cell.myExpenseName.text = myExpensesArray [indexPath.row]
cell.myExpenseAmount.text = myAmountArray [indexPath.row]
return cell
}
func loadData (){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "UserExpenses")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request)
// check data existance
if results.count>0 {
print(results.count)
for resultGot in results as! [NSManagedObject]{
if let expName = resultGot.value(forKey:"expenseName") as? String{
myExpensesArray += [expName]
DispatchQueue.main.async {
[unowned self] in self.expensesTableView.reloadData()
self.expensesTableView.reloadData()
}
print("myExp array is : \(myExpensesArray)")
}
if let amountVal = resultGot.value(forKey:"amount") as? String{
myAmountArray += [amountVal]
DispatchQueue.main.async {
[unowned self] in self.expensesTableView.reloadData()
self.expensesTableView.reloadData()
}
print("myAmount array is : \(myAmountArray)")
}
}
}
}catch{
print("No Data to load in the array")
}
}
}
You have to add 2 more methods for delete row on swipe
//For swipe access allow
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
//For remove row from tableview & object from array.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
// delete data and row
<YourArray>.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
let me know if you need more detail.