Retrieve and display multiple images from URL - swift

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.

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 {
}

Reusable UITableView for Varying Data Input - Swift/Xcode

I have a TableView that I want to reuse for different categories of data (essentially as plugins.. the tableView being a skeleton and being filled with whatever I want it to be). The TableView is filled with different categories of data (and related actions) depending on essentially what ViewController the user came from. I understand how to make it display the various data (just send it whatever array I want it to display), but I can't figure out how I could control the actions for the data at the specific index selected in didSelectRowAtIndexPath.
How could I do this? How could I create an array that has both Strings and executable actions associated with each indices? For example:
arrayOneNames = ["Tigris", "Leo", "Barko"]
arrayOneActions = [displayTheTigers, displayTheCats, displayTheDogs]
If "Leo" is selected in the tableView, then "displayTheCats" is executed. Again, I want each array to be a separate Class that I can use as a plugin, so that I can fill the tableView with whichever Class of data I want it to display and execute, depending on which ViewController the user came from previously. Please answer in Swift.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell=UITableViewCell() // cell you've created
cell.data = arrayOneNames[indexPath.row] // passing relevant data
cell.tag = indexPath.row // the tag you want to pass for particular data
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)! as UITableViewCell // take selected cell
if(cell.tag == 0) { // call relevant function accordingly.
self.tigrisTouch()
} else if (cell.tag == 1) {
self.leoTouch()
} else {
self.barkoTouch()
}
}
private func tigrisTouch() {
}
private func leoTouch() {
}
private func barkoTouch() {
}

How to load tableview dynamically in swift 4

I have used Worm Tab Strip cocoapod to get tabs like android, the name of tabs are from server response, I need n number of view controllers based on the number of tab names.
For testing purpose, I have hardcoded tab names, and for each particular tab name, I have another array of sub names. My question is how do I change the tableview contents so that, I get an exact number of sub names according to the tab names
var tabNames = ["Brands", "Sports","Movies", "Mobile","Games"]
var brandsNames = ["Addidas", "Nike","Puma"]
var sportsName = ["Cricket","Fifa","Hockey","Baseball"]
var moviesName = ["Mission Impossible","Matrix","Avatar","Titanic"]
var mobileNames = ["Nokia","Redmi","Samsung"]
var gameNames = ["FIFA 19","PES 19","WWE 2K19","Max Payne"]
What should i try in
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
// return fruits.count
}
And
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell",
for: indexPath) as! Cell1
//cell.textLabel?.text = fruits[indexPath.row]
return cell
}
I need to get for Brands tab, I need brandsNames as tableview
contents.
The tableview contents must change according to the tab names.
Create a custom struct as data model for example
struct Category {
let name : String
let items : [String]
}
Declare a data source array and an index
var categories = [Category]()
var index = 0
In viewDidLoad populate the data source array and set index of the current index of the UISegmentedControl
categories = [Category(name:"Brands", items: ["Addidas", "Nike","Puma"]),
Category(name:"Sports", items: ["Cricket","Fifa","Hockey","Baseball"]),
Category(name:"Movies", items: ["Mission Impossible","Matrix","Avatar","Titanic"]),
Category(name:"Mobile", items: ["Nokia","Redmi","Samsung"]),
Category(name:"Games", items: ["FIFA 19","PES 19","WWE 2K19","Max Payne"])]
index = // current index of the segmented control
in the IBAction of the segmented control set the index and reload the table view
#IBAction func categorySelection(_ sender: UISegmentedControl) {
index = sender.selectedSegmentIndex
tableView.reloadData()
}
The data source methods are
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories[index].items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Cell1
cell.textLabel?.text = categories[index].items[indexPath.row]
return cell
}
I used this enter link description here as a reference So that I can pass ID of particular tab to tableview in viewcontroller and load that Tablview, So now I can dynamically add new tabs and the corresponding Tablview also changes

How to bind Switch state to an object in 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
}
}

UISegment value changing when tableview get scrolled

I am using UISegmentControl to display objective type questions in table view. But, if I select one segment in any one of cell, then if I scroll, some segment values are changed. I dont know how to solve that.
Kindly guide me.
Cell size : 160px
Segment tint color : blue color
Coding
//UIViewController
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = segTblVw.dequeueReusableCellWithIdentifier("segment", forIndexPath: indexPath) as! segmentTblCell
return cell
}
//UITableViewCell CLASS
class segmentTblCell: UITableViewCell {
#IBOutlet weak var segMent: UISegmentedControl!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Screen shot below:
You're having this problem because of how dequeueReusableCellWithIdentifier: works. Each time a cell get scrolled out of screen, it enters a cache area where it will be reused.
Let's say you have 100 cells. All their segmentedControl objects are set to first. You tap on one to change it's value. As the cell moves out of view, it enters the cache, where it will be dequeued if you scroll down further.
It's important to understand this, because the segmentedControl object is not actually changing. It looks like it's changing because of the dequeue behaviour.
To solve this problem, you will need to implement a dataSource that stores the segmentedControl object's value so you can reinitialize it correctly every time a cell is dequeued.
Method 1: Prevent reusability of cells by, Holding all cell objects in an array
var arraysCells : NSMutableArray = []//globally declare this
in viewDidLoad()
for num in yourQuestionArray//this loop is to create all cells at beginning
{
var nib:Array = NSBundle.mainBundle().loadNibNamed("SegmentTableViewCell", owner: self, options: nil)
var cell = nib[0] as? SegmentTableViewCell
arraysCells.addObject(cell!);
}
in tableViewDelegate,
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return arraysCells.objectAtIndex(indexPath.row) as! UITableViewCell
}
you can find the selected segment values (answer) by iterating arraysCells
NOTE: Method 1 will be slow, if you have big number of cells
Method 2: Reuse the cell as normal, but save the states(enterd values) Using Delegate and arrays.
in custom UITableViewCell
#objc protocol SegmentTableViewCellDelegate {
func controller(controller: SegmentTableViewCell, selectedSegmentIndex:Int, indexPath : NSIndexPath)
}
class SegmentTableViewCell: UITableViewCell {
var delegate: AnyObject?
var indexPath : NSIndexPath?
#IBOutlet weak var segment: UISegmentedControl! //outlet of segmented Control
#IBAction func onSegmentValueChanged(sender: UISegmentedControl/*if the parameter type is AnyObject changed it as UISegmentedControl*/)//action for Segment
{
self.delegate?.controller(self, selectedSegmentIndex: sender.selectedSegmentIndex, indexPath: indexPath!)
}
in viewController
class MasterViewController: SegmentTableViewCellDelegate{
var selectedAnswerIndex : NSMutableArray = [] //globally declare this
var selectedSegmentsIndexPath : NSMutableArray = [] //globally declare this
func controller(controller: SegmentTableViewCell, selectedSegmentIndex:Int, indexPath : NSIndexPath)
{
if(selectedSegmentsIndexPath.containsObject(indexPath))
{
selectedAnswerIndex.removeObjectAtIndex(selectedSegmentsIndexPath.indexOfObject(indexPath))
selectedSegmentsIndexPath.removeObject(indexPath)
}
selectedAnswerIndex.addObject(selectedSegmentIndex)
selectedSegmentsIndexPath.addObject(indexPath)
}
in cellForRowAtIndexPath (tableView Delegate)
if(selectedSegmentsIndexPath.containsObject(indexPath))
{
cell?.segment.selectedSegmentIndex = selectedAnswerIndex.objectAtIndex(selectedSegmentsIndexPath.indexOfObject(indexPath)) as! Int
}
cell?.delegate = self
cell?.indexPath = indexPath
you can get the result by
for index in selectedSegmentsIndexPath
{
var cellIndexPath = index as! NSIndexPath
var answer : Int = selectedAnswerIndex.objectAtIndex(selectedSegmentsIndexPath.indexOfObject(cellIndexPath)) as! Int
NSLog("You have enterd answer \(answer) for question number \(cellIndexPath.row)")
}
#KelvinLau's is perfect
you can do that by using var segmentedTracker : [NSIndexPath:Int] = [:]
on segmentedValueChanged set the value of the selectedIndex ie: segmentedTracker[indexPath] = valueOf the selected index
then in cellForRowAtIndexPath check for the value let selected = [segmentedTracker]
cell.yourSegmentedControlReference.selectedIndex = selected
please note this is a pseudocode I don't remember the properties name. From here you can figure it out by urself
I think to use UISegmentControl in UITableViewCell may be wrong.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UISegmentedControl_Class/
I have never seen the kind of that in iOS application.
The problem is that UITableViewCell is reused by dequeueReusableCellWithIdentifier method. So some UISegmentControl values are changed when scrolling.
Although it is not best solution, you can use Static Cells. What you need to do is that only switch Static cells. And If so, you don't write code of UITableViewCell.
Year 2018: Updated Answer
Find my easiest answer in this UISegement inside UITableViewCell
=======================================================
Year 2015
I have tested in my own way. My coding is below. Kindly guide me, whether it is right way or wrong way? My problem get solved. This code stops reusable cell.
My Coding Below:
//UIViewController
var globalCell = segmentTblCell() //CUSTOM UITableViewCell Class
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = segTblVw.dequeueReusableCellWithIdentifier("segment", forIndexPath: indexPath) as! segmentTblCell
globalCell = segTblVw.dequeueReusableCellWithIdentifier("segment", forIndexPath: indexPath) as! segmentTblCell //THIS LINE - STOPS REUSABLE TABLE.
return cell
}