How to bind Switch state to an object in Swift? - swift

In my app I have a switch and I want it to be an indicator for saving images. For now I have just a button that saves all the images.
It just makes more sense with an example
What I've tried:
func saveTapped() {
let cell = collectionView?.cellForItem(at: indexPath) as! CustomCell
for image in images where cell.savingSwitch.isOn {
...
But I can't access indexPath. How should I call this Save method in order to access a particular row in my collectionView? Or is there another way?

First, you'll need a way to store the "save" settings alongside each image in your table, e.g. by keeping the image and the flag in a struct:
struct TableEntry {
let image: UIImage
var save = false
}
and use a var images: [TableEntry] in the data source of your tableview.
You can then use the tag property of each UISwitch to store the row it's in, e.g. in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(...)
cell.switch.tag = indexPath.row
cell.switch.isOn = self.images[indexPath.row].save
cell.imageView.image = self.images[indexPath.row].image
return cell
}
and then use the tag in the method that's called when the switch value changes to know which image is being referred to:
#IBAction func switchChanged(_ sender: UISwitch) {
self.images[sender.tag].save = sender.isOn
}
func saveTapped() {
let imagesToSave = self.images.filter { $0.save }
}

In your CustomCell, you can add a closure to be fired when switch state is changed like below,
class CustomCell: UITableViewCell {
var onSwitchStateChange: ((Bool) -> Void)?
#IBAction func switchTapped(_ sender: UISwitch) {
self.onSwitchStateChange?(sender.isOn)
}
}
then you can update your cellForRowAt like below,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.onSwitchStateChange = { state in
guard state else { return }
let image = images[indexPath.row]
// Upload Image
}
}

Related

How to make optional images in a UITableViewCell?

Hi I'm making an app with UIkit and I'm having a problem trying to achieve the layout of a cell.
What I want is to make a cell that has an image header, a stack of user images, and a label. I place this cell in a UITableView. The problem I'm having is that the image header is optional so if it's not there I need it to display like the cell shown below in the attached image.
Can someone who has more knowledge in UIkit help me please?
let suppose you have struct that use to populate tableview
struct User {
let name:String
let headerImage:UIImage? // an optional image
}
your controller
class ViewController:UIViewController {
var users = [User]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let image = users[indexPath.row].image {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserHeaderCell", for: indexPath) as! UserHeaderCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserSimpleCell", for: indexPath) as! UserSimpleCell
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let image = users[indexPath.row].image {
return 100 // with image
}
return 50 // without image
}
}
make two cell one for header image and one for simple
class UserHeaderCell:UITableViewCell {
}
class UserSimpleCell:UITableViewCell {
}

How to maintain state of switch button inside tableviewcell with searchbar in xcode

Description : i have a content in tableview along with switch button .
Problem : after using the searchbar for serching items in tableview state of switch button is lost - switch button which was set on for row 0 and row 1 has lost .
To achieve this you have to store button state with your data.
And Read the data in cellForRowAtIndexPath: Handle UI
Example:
if indexPath.row == 0 || indexPath.row == 1{
// Handle Button UI here
let value = buttonArrayData[indexPath.row]
if(value){
//button selected
//update buttonArrayData data accordingly.
}
else{
//button unselected
//update buttonArrayData data accordingly.
}
}
It is not a problem associated with UISearchBar but seems because of TableView reload.
To save the state of UISwitch in the case of table reload, you need have a custom UITableViewCell class and UISwitch outlet inside it.
Then to save a UISwitch state for further use
You can store only the selected cell index as state in UserDefault and check the same against the current index in cellForRowAt tableView method. If it is found then your last state is ON otherwise default one is OFF.
This code snippet will work as you expect:
Custom UITableViewCell class
class CustomCell: UITableViewCell {
#IBOutlet weak var switchObj: UISwitch!
#IBOutlet weak var textValue: UILabel!
#IBAction func toggleState(_ sender: Any) {
var loadValues: [Int: Bool]!
if let values = Memory.getStatesFromMemory() {
loadValues = values
} else {
loadValues = [Int: Bool]()
}
loadValues[switchObj.tag] = switchObj.isOn
Memory.saveSwitchStatesToMemory(savedStates: loadValues)
}
}
UIViewController class
class CustomTableView: UIViewController, UITableViewDelegate, UITableViewDataSource {
var savedStates: [Int: Bool]!
override func viewDidLoad(){
super.viewDidLoad()
if let values = Memory.getStatesFromMemory() {
savedStates = values
} else {
savedStates = [Int: Bool]()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as? CustomCell
cell?.switchObj.isOn = savedStates[indexPath.row] == nil ? false : savedStates[indexPath.row]!
cell?.switchObj.tag = indexPath.row
cell?.textValue.text = "Switch stored state >>"
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
}
Custom class to use for save/retrieve values from memory
class Memory {
static func getStatesFromMemory() -> [Int: Bool]? {
if let switchValues = UserDefaults.standard.object(forKey: "SwitchStates") as? Data {
let decodedTeams = NSKeyedUnarchiver.unarchiveObject(with: switchValues) as! [Int: Bool]
return decodedTeams
}
return nil
}
static func saveSwitchStatesToMemory(savedStates: [Int: Bool]) {
let encodedData = NSKeyedArchiver.archivedData(withRootObject: savedStates)
let userDefaults = UserDefaults.standard
userDefaults.set(encodedData, forKey: "SwitchStates")
UserDefaults.standard.synchronize()
}
}

Can't get indexPath of cell in header

I have created prototype custom header cell for a tableView with a button on it. I am trying to get the indexPath of the cell when the button is tapped, but I don't receive it. Any idea what I am doing wrong here?
protocol MediaHeaderCellDelegate: class {
func editPost(cell: MediaHeaderCell)
}
class MediaHeaderCell: UITableViewCell {
weak var delegate: MediaHeaderCellDelegate?
#IBAction func moreOptionsAction(_ sender: UIButton) {
delegate?.editPost(cell: self)
}
}
class NewsfeedTableViewController:UITableViewController, MediaHeaderCellDelegate {
func editPost(cell: MediaHeaderCell) {
guard let indexPath = tableView.indexPath(for: cell) else {
print("indexpath could not be given")
return}
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.mediaHeaderCell) as! MediaHeaderCell
cell.delegate = self
cell.media = media[section]
return cell
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.mediaCell, for: indexPath) as! MediaTableViewCell
cell.currentUser = currentUser
cell.media = media[indexPath.section]
cell.delegate = self
return cell
}
}
So this is actually all about learning what section a section header belongs to?? Here’s what I do. I have a header class:
class MyHeaderView : UITableViewHeaderFooterView {
var section = 0
}
I register it:
self.tableView.register(
MyHeaderView.self, forHeaderFooterViewReuseIdentifier: self.headerID)
I use and configure it:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let h = tableView
.dequeueReusableHeaderFooterView(withIdentifier: self.headerID) as! MyHeaderView
// other stuff
h.section = section // *
return h
}
Now if the header view is tappable or contains a button or whatever, learning what section this is the header of is trivial.
Your immediate issue is that you are using a table cell as a section header view. That should not be done. Once you resolve that, your next task is to determine the table section from the header view whose button was tapped.
First, change your MediaHeaderCell to be a header view that extends UITableViewHeaderFooterView and update your protocol accordingly:
protocol MediaHeaderViewDelegate: class {
func editPost(view: MediaHeaderView)
}
class MediaHeaderView: UITableViewHeaderFooterView {
weak var delegate: MediaHeaderViewDelegate?
#IBAction func moreOptionsAction(_ sender: UIButton) {
delegate?.editPost(cell: self)
}
}
Then you need to register the header view in your view controller.
Then update your viewForHeaderInSection:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: Storyboard.mediaHeaderView) as! MediaHeaderView
view.delegate = self
view.media = media[section]
view.tag = section
return view
}
And last, update your protocol method implementation:
func editPost(view: MediaHeaderView) {
let section = view.tag
// do something
}
There is one possible issue with this. If your table allows sections to be added or removed, then it is possible that a header view's tag could be wrong when the button is tapped.

Retrieve and display multiple images from URL

Experimenting with Swift3, Alamofire and AlamofireImage. I am trying to display some Instagram images in a tableview. I have a data.json file of Places retrieved using Alamofire which look like this:
{
"places" : [
{ "name" : "place1", "url" : "http://instagramImage1.jpg" },
{ "name" : "place2", "url" : "http://instagramImage2.jpg" },
{ "name" : "place3", "url" : "http://instagramImage3.jpg" },
]
}
I have setup PlaceTableViewCell with a UIImageView and attached it to an outlet in the corresponding controller.
I also have a PlaceTableViewController which includes:
var places = [Place]() //empty array to store places
In viewDidLoad() method I call a private function:
loadJson()
and the function looks like this:
private func loadJson() {
Alamofire.request("https://example.com/data.json").responseJSON { response in
if let JSON = response.result.value as? [String : Any],
let places = JSON["places"] as? [[String : String]] {
for place in places {
//should I call loadImage() function here?
}
}
}
}
I also have a model for each place in the JSON file:
import UIKit
class Place {
var name: String
var url: String
init?(name: String, url: String) {
self.name = name
self.url = url
}
}
QUESTION
I'm not sure where to go from here. I would like to use AlamofireImage (which I have included in my project) to download each image but can I do this in a loop of some sort? I would also like to show each image in a new table row. Any advice and code examples would be much appreciated.
I would recommend not fetch the image in loadJSON.
Why?
There could be a large number of photos coming back in the initial request, and the user may never even scroll down in the application far enough to see some of them.
On top of that, if you initialize too many requests simultaneously, some of the requests may time out while waiting for other requests to finish. So this is probably not the best solution.
So, now coming to the solution. It makes sense to download the image data for only the cells that the user is attempting to view.
TableView has a delegate method for this purpose only
optional func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath)
Use this method to load the images.
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
let photoURL = places[indexPath.row].url
// fetch this photo from web using URL
// get the table view cell and update the image
if let cell = self.tableView.cellForRow(at: indexPath) as UITableViewCell {
cell.imageView.image = //fetchPhoto
}
}
}
You can use below code in your tableview controller to populate image in cell. For that you will also require to create one cell in tableview or create custom cell with xib. My code is for loading the custom cell from xib take a look.
//MARK:- TableView Delegate and DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int {
return places.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200//or cell height you want
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "PlaceCell") as? PlaceCell
if cell == nil {
cell = (loadFromNibNamed(viewClass: PlaceCell.self) as! PlaceCell)
}
cell?.ivCategory.af_setImage(withURL: URL(string: places[indexPath.row].url))
return cell!
}
Also note that you will have to reload tableview once you get response from api.

Delete row in TableView and remove the core data - iOS / Swift

I am having an issue with my swift app. I am trying to implement the swipe feature that will prompt the user with the "DELETE" option. As of right now my code only allows me to swipe but the delete button is not being generated. I am currently using Xcode 6.3.2 as well.
Thank you in advance!
import UIKit // apples code for all UI related code
import AVFoundation // apple's code for audio and video
import CoreData
class SoundListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var audioPlayer = AVAudioPlayer() // creating an audio player property as the variable audioplayer
var sounds : [Sound] = [] // creating an array to hold all sounds, in swift you need to tell the array what it will be holding, this is done by ": [Sound]" this tells the array that it will contain Sound objects
override func viewDidLoad() {
super.viewDidLoad()
// comment
self.tableView.dataSource = self // added to make the Table view work
self.tableView.delegate = self // added to make the Table view work
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// comment
var context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
var request = NSFetchRequest(entityName: "Sound") // allows to go get all the objects stored in core data
self.sounds = context.executeFetchRequest(request, error: nil)! as! [Sound]
self.tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sounds.count // # of rows we want the table view to have based on the contents of sounds array
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var sound = self.sounds[indexPath.row] // searches the index path of each row from the array of sounds and stores to the varibale sounds once it has the correct object
var cell = UITableViewCell() // each cell on the table
cell.textLabel!.text = sound.name // adds the correct name from each object to the correct row
if(indexPath.row % 2 == 0){ // adding color to each cell
cell.backgroundColor = UIColor.lightGrayColor()
cell.textLabel?.textColor = UIColor.whiteColor()
}else if (indexPath.row % 2 != 0){
cell.backgroundColor = UIColor.whiteColor()
}
return cell // printing the new data into each cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var sound = self.sounds[indexPath.row]
var baseString : String = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0] as! String
var pathComponents = [baseString, sound.url]
var audioNSURL = NSURL.fileURLWithPathComponents(pathComponents)
self.audioPlayer = AVAudioPlayer(contentsOfURL: audioNSURL, error: nil) // play command while asking for where the audio lives
self.audioPlayer.play() // play audio command on click
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete){
sounds.removeAtIndex(indexPath.row) // array of objects that we are selecting
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation:.Fade)
self.tableView.reloadData()// updated table view
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var nextViewController = segue.destinationViewController as! newSoundViewContoller
nextViewController.soundListViewController = self
}
}
I can't find your issues specifically but. Here is some code that worked for me. Let me know if it works for you.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.dataSource = self
self.tableView.delegate = self
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
cell.textLabel!.text = "Delete me!"
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete){
println("DELETED")
}
}
}
SOLVED
The issue with my delete was not the issue. (NOTE: you do not need the "tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation:.Fade)" line in your delete function.)
The issue I was originally having was that the slide would work but the user would not be prompt with the delete.
Make sure you check your constraints with your tableView. Also make sure you uncheck the "Constraint to margins" option.