UICollectionViewCompositionalLayout optional sections - ios

I have looked at this question, which is similar: How to deal with empty items section in UICollectionView CompositionalLayout, but the answer there seems to be either leave sections out in the snapshot (which I do, but that leaves another problem, which I describe later) or to render a very small section. That solution does not seem like a good solution.
I have a collection view using a compositional layout with a diffable data source. The collection view has four sections, but each of those sections is optional meaning that if the corresponding data for that section is empty, then the section should not be displayed.
Code
Layout Definition
I have a section provider that uses the sectionIndex to configure what each section should look like. I think this is bad because then if I do not have data for section three in the snapshot, for instance, then everything that should normally be in section four will now have an indexPath that will cause it to be laid out like section three.
And each section has different item sizes and some are orthogonal scrolling sections. So if section four data is rendered using the section three layout, then it will look wrong.
NSCollectionLayoutSection * _Nullable (^sectionProvider)(NSInteger, id<NSCollectionLayoutEnvironment> _Nonnull) = ^NSCollectionLayoutSection * _Nullable (NSInteger sectionIndex, id<NSCollectionLayoutEnvironment> _Nonnull layoutEnvironment) {
if (sectionIndex == 0) {
//configure and return a layout for the first section
} else if (sectionIndex == 1) {
//configure and return a layout for the second section
} else if (sectionIndex == 2) {
//configure and return a layout for the third section
} else if (sectionIndex == 3) {
//configure and return a layout for the fourth section
}
return nil;
};
UICollectionViewCompositionalLayoutConfiguration *configuration = [[UICollectionViewCompositionalLayoutConfiguration alloc] init];
configuration.interSectionSpacing = 10;
configuration.scrollDirection = UICollectionViewScrollDirectionVertical;
self->_collectionViewLayout = [[UICollectionViewCompositionalLayout alloc] initWithSectionProvider:sectionProvider configuration:configuration];
Data Source Definition
This is where the data source is defined. Each section uses a different data model class, so I decide which type of cell to use based on the type of the data model class, not on the index path.
self->_dataSource = [[UICollectionViewDiffableDataSource alloc] initWithCollectionView:self.collectionView cellProvider:^UICollectionViewCell * _Nullable(UICollectionView * _Nonnull collectionView, NSIndexPath * _Nonnull indexPath, id _Nonnull item) {
if ([item isKindOfClass:[MyFirstSectionModel class]]) {
return [collectionView dequeueConfiguredReusableCellWithRegistration:firstSectionCellRegistration forIndexPath:indexPath item:item];
} else if ([item isKindOfClass:[MySecondSectionModel class]]) {
return [collectionView dequeueConfiguredReusableCellWithRegistration:secondSectionCellRegistration forIndexPath:indexPath item:item];
} else if ([item isKindOfClass:[MyThirdSectionModel class]]) {
return [collectionView dequeueConfiguredReusableCellWithRegistration:thirdSectionCellRegistration forIndexPath:indexPath item:item];
} else if ([item isKindOfClass:[MyFourthSectionModel class]]) {
return [collectionView dequeueConfiguredReusableCellWithRegistration:fourthSectionCellRegistration forIndexPath:indexPath item:item];
}
return nil;
}];
Snapshot Construction
Here is where each section is either included (if it has data) or excluded (if the section is empty). But leaving a section out (like for example, if section three does not have any data, then it will be left out, but then that will make section four's data to have an index path with an index of 2, which will not work with the section provider.
If I insert an empty section into the snapshot, that still will not work because some of these sections have headers, so if it is a section that has a header then the header will still be displayed. But even if none of the sections had headers, I think it would still render some extra amount of empty space for the section (but this may be incorrect).
- (void)reloadDataSourceAnimated:(BOOL)animated {
NSDiffableDataSourceSnapshot<CICustomerReviewsSectionIdentifierType, CICustomerReviewsItemIdentifierType> *snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
if (self.firstSectionItems.count) {
[snapshot appendSectionsWithIdentifiers:#[MyFirstSectionIdentifier]];
[snapshot appendItemsWithIdentifiers:#[self.firstSectionItems] intoSectionWithIdentifier:MyFirstSectionIdentifier];
}
if (self.secondSectionItems.count) {
[snapshot appendSectionsWithIdentifiers:#[MySecondSectionIdentifier]];
[snapshot appendItemsWithIdentifiers:#[self.secondSectionItems] intoSectionWithIdentifier:MySecondSectionIdentifier];
}
if (self.thirdSectionItems.count) {
[snapshot appendSectionsWithIdentifiers:#[MyThirdSectionIdentifier]];
[snapshot appendItemsWithIdentifiers:#[self.thirdSectionItems] intoSectionWithIdentifier:MyThirdSectionIdentifier];
}
if (self.fourthSectionItems.count) {
[snapshot appendSectionsWithIdentifiers:#[MyFourthSectionIdentifier]];
[snapshot appendItemsWithIdentifiers:self.fourthSectionItems intoSectionWithIdentifier:MyFourthSectionIdentifier];
}
[self.dataSource applySnapshot:snapshot animatingDifferences:animated];
}
Summary
So the problem is that if one or more of my sections does not have data, then when they get left out of the snapshot, that will cause the data for subsequent sections to be rendered in the wrong section (because the section provider configures sections based on the index and the indexPaths of each of the sections after the empty section(s) are no longer the original indexPath).
Question
Is there a way to have the sections be optional and for any regular views and supplementary views to not be rendered for an "empty" section?

I solved this problem by assigning my collection view data to local variable before applying datasource snaphot. This variable can be accessed by UICollectionViewCompositionalLayoutSectionProvider closure for determine which layout needs to be returned for a given index.
Example
Lets take this data model:
struct ViewControllerData {
let texts: [String]
let colors: [UIColor]
let numbers: [Int]
}
Collection view datasource definition:
enum Section: Hashable {
case first
case second
case third
}
enum SectionData: Hashable {
case text(String)
case color(UIColor)
case number(Int)
}
lazy var datasource: UICollectionViewDiffableDataSource<Section, SectionData> = {
let dataSource = UICollectionViewDiffableDataSource<Section, SectionData>(collectionView: self.collectionView) { [weak self] (collectionView, indexPath, data) -> UICollectionViewCell? in
switch data {
case .text(let text):
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TextCollectionViewCell.reuseIdentifier, for: indexPath) as? TextCollectionViewCell
cell?.textLabel.text = text
return cell
case .color(let color):
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ColorCollectionViewCell.reuseIdentifier, for: indexPath) as? ColorCollectionViewCell
cell?.colorView.backgroundColor = color
return cell
case .number(let number):
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NumberCollectionViewCell.reuseIdentifier, for: indexPath) as? NumberCollectionViewCell
cell?.numberLabel.text = "\(number)"
return cell
}
}
dataSource.supplementaryViewProvider = ...
return dataSource
}()
Configure diffable snapshot excluding sections which has no data and assign model data to a local variable:
private var currentData: ViewControllerData?
public func showData(_ data: ViewControllerData) {
self.currentData = data
var snapshot = NSDiffableDataSourceSnapshot<Section, SectionData>()
if !data.texts.isEmpty {
snapshot.appendSections([.first])
snapshot.appendItems(data.texts.map { SectionData.text($0 )}, toSection: .first)
}
if !data.colors.isEmpty {
snapshot.appendSections([.second])
snapshot.appendItems(data.colors.map { SectionData.color($0) }, toSection: .second)
}
if !data.numbers.isEmpty {
snapshot.appendSections([.third])
snapshot.appendItems(data.numbers.map { SectionData.number($0) }, toSection: .third)
}
datasource.apply(snapshot, animatingDifferences: true)
}
Use this variable to provide correct section layout:
lazy var collectionViewLayout: UICollectionViewLayout = {
let layout = UICollectionViewCompositionalLayout { [weak self] (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
guard let section = self?.currentData?.visibleSection(at: sectionIndex) else { return nil }
switch section {
case .first:
let section = ...
return section
case .second:
let header = ...
let section = ...
section.boundarySupplementaryItems = [header]
return section
case .third:
let section = ...
return section
}
}
return layout
}()
visibleSection(at index:) is extension of ViewControllerData for convenience:
extension ViewControllerData {
var visibleSections: [ViewController.Section] {
var sections: [ViewController.Section] = []
if !texts.isEmpty { sections.append(.first) }
if !colors.isEmpty { sections.append(.second) }
if !numbers.isEmpty { sections.append(.third) }
return sections
}
func visibleSection(at index: Int) -> ViewController.Section? {
guard visibleSections.indices.contains(index) else { return nil }
return visibleSections[index]
}
}
This variable can also be used in collection view data source for provide supplementary views:
dataSource.supplementaryViewProvider = { [weak self] (collectionView, kind, indexPath) in
guard let section = self?.currentData?.visibleSection(at: indexPath.section) else { return nil }
switch section {
case .second:
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HeaderView.reuseIdentifier, for: indexPath) as? HeaderView
header?.textLabel.text = "Colors section header"
return header
default: return nil
}
}
Result:

Related

RXSwift - RxCollectionViewSectionedReloadDataSource - Drag & drop move (Reorder)

I have used RxCollectionViewSectionedReloadDataSource to load my data into UICollectionView.
let dataSource = RxCollectionViewSectionedReloadDataSource<SectionModel<String, WorkGroup>>(
configureCell: { (_, collectionView, indexPath, item) in
guard let cell = collectionView
.dequeueReusableCell(withReuseIdentifier: WorkGroupCell.identifier, for: indexPath) as? WorkGroupCell else {
return WorkGroupCell()
}
cell.viewModel = item
return cell
}
)
viewModel.items.bind(to: tileCollectonView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
tileCollectonView.rx.setDelegate(self).disposed(by: disposeBag)
Using above code i can display the data. But i want to drag and drop(Reorder) the cell.
Please let me know how can i do that reorder using RxSwift.
Help will be highly appreciated.
As is often the case, you need a state machine.
The logic involved is quite simple:
struct State<Item> {
var items: [Item] = []
}
func state<Item>(initialState: State<Item>, itemMoved: Observable<ItemMovedEvent>) -> Observable<State<Item>> {
itemMoved
.scan(into: initialState) { (state, itemMoved) in
state.items.move(from: itemMoved.sourceIndex.row, to: itemMoved.destinationIndex.row)
}
.startWith(initialState)
}
extension Array
{
mutating func move(from oldIndex: Index, to newIndex: Index) {
if oldIndex == newIndex { return }
if abs(newIndex - oldIndex) == 1 { return self.swapAt(oldIndex, newIndex) }
self.insert(self.remove(at: oldIndex), at: newIndex)
}
}
The above uses the ItemMovedEvent provided by RxCocoa for table views. In order to create something like that for a collection view, you will need to wrap the UICollectionViewDragDelegate in a delegate proxy. An article on how to do that can be found here: Convert a Swift Delegate to RxSwift Observables

UITableView Async image not always correct

I have a UITableView and during the initial loading of my app it sends multiple API requests. As each API request returns, I add a new row to the UITableView. So the initial loading adds rows in random orders at random times (Mostly it all happens within a second).
During cell setup, I call an Async method to generate an MKMapKit MKMapSnapshotter image.
I've used async image loading before without issue, but very rarely I end up with the image in the wrong cell and I can't figure out why.
I've tried switching to DiffableDataSource but the problem remains.
In my DiffableDataSource I pass a closure to the cell that is called when the image async returns, to fetch the current cell in case it's changed:
let dataSource = DiffableDataSource(tableView: tableView) {
(tableView, indexPath, journey) -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: "busCell", for: indexPath) as! JourneyTableViewCell
cell.setupCell(for: journey) { [weak self] () -> (cell: JourneyTableViewCell?, journey: Journey?) in
if let self = self
{
let cell = tableView.cellForRow(at: indexPath) as? JourneyTableViewCell
let journey = self.sortedJourneys()[safe: indexPath.section]
return (cell, journey)
}
return(nil, nil)
}
return cell
}
Here's my cell setup code:
override func prepareForReuse() {
super.prepareForReuse()
setMapImage(nil)
journey = nil
asyncCellBlock = nil
}
func setupCell(for journey:Journey, asyncUpdateOriginalCell:#escaping JourneyOriginalCellBlock) {
self.journey = journey
// Store the async block for later
asyncCellBlock = asyncUpdateOriginalCell
// Map
if let location = journey.location,
(CLLocationCoordinate2DIsValid(location.coordinate2D))
{
// Use the temp cached image for now while we get a new image
if let cachedImage = journey.cachedMap.image
{
setMapImage(cachedImage)
}
// Request an updated map image
journey.createMapImage {
[weak self] (image) in
DispatchQueue.main.async {
if let asyncCellBlock = self?.asyncCellBlock
{
let asyncResult = asyncCellBlock()
if let cell = asyncResult.cell,
let journey = asyncResult.journey
{
if (cell == self && journey.id == self?.journey?.id)
{
self?.setMapImage(image)
// Force the cell to redraw itself.
self?.setNeedsLayout()
}
}
}
}
}
}
else
{
setMapImage(nil)
}
}
I'm not sure if this is just a race condition with the UITableView updating several times in a small period of time.
I think this is because when the image is available then that index is not there. Since the table view cells are reusable, it loads the previous image since the current image is not loaded yet.
if let cachedImage = journey.cachedMap.image
{
setMapImage(cachedImage)
}
else {
// make imageView.image = nil
}
I can see you already cache the image but I think you should prepare the cell for reuse like this:
override func prepareForReuse() {
super.prepareForReuse()
let image = UIImage()
self.yourImageView.image = image
self.yourImageView.backgroundColor = .black
}

Swift - Firebase - Downloading images and data asynchronously leads to wrong display within the collection view cell

I have a collection view and want to load images and other data asynchronously from firebase and display them within the cell. However, my current approach displays wrong images to the text data (they simply don't fit) and also, the image in the one specific cell changes few times until it settles down (sometime wrong, sometimes correct).
My code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let photoCell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainViewCollectionCell", for: indexPath) as! MainViewCollectionViewCell
// issue when refreshing collection view after a new challenge has been created
if (hitsSource?.hit(atIndex: indexPath.row) == nil) {
return photoCell
}
let challengeObject = Challenge(json: (hitsSource?.hit(atIndex: indexPath.row))!)
let group = DispatchGroup()
group.enter()
// async call
self.checkIfChallengeIsBlocked(completionHandler: { (IsUserBlocked) in
if (IsUserBlocked) {
return
}
else {
group.leave()
}
}, challengeObject: challengeObject)
group.notify(queue: .main) {
photoCell.setChallengeLabel(title: challengeObject.title)
// async call
photoCell.fetchChallengeImageById(challengeObject: challengeObject)
photoCell.checkIfToAddOrRemovePlayIcon(challengeObject: challengeObject)
// async call
self.dataAccessService.fetchUserById(completionHandler: { (userObject) in
photoCell.setFullName(userObject: userObject)
photoCell.setStarNumber(challengeObject: challengeObject)
}, uid: challengeObject.organizerId)
// async all
self.dataAccessService.fetchAllParticipantsByChallengeId(completionHandler: { (participationArray) in
photoCell.setParticipationNumber(challengeObject: challengeObject, participationArray: participationArray)
}, challengeId: challengeObject.id)
// resize image to collection view cell
self.activityView.removeFromSuperview()
}
return photoCell
}
...
Just to show you my MainViewCollectionViewCell
class MainViewCollectionViewCell: UICollectionViewCell {
...
public func fetchChallengeImageById(challengeObject:Challenge) {
self.dataAccessService.fetchChallengeImageById(completion: { (challengeImage) in
self.challengeImageView.image = challengeImage
self.layoutSubviews()
}, challengeId: challengeObject.id)
}
and DataAccessService.swift
class DataAccessService {
...
// fetch main challenge image by using challenge id
public func fetchChallengeImageById(completion:#escaping(UIImage)->(), challengeId:String) {
//throws {
BASE_STORAGE_URL.child(challengeId).child(IMAGE_NAME).getData(maxSize: 1 * 2048 * 2048,
completion:({ data, error in
if error != nil {
print(error?.localizedDescription as Any)
let notFoundImage = UIImage()
completion(notFoundImage)
} else {
let image = UIImage(data: data!)!
completion(image)
}
}))
}
...
public func fetchUserById(completionHandler:#escaping(_ user: User)->(), uid:String?) { //
throws{
var userObject = User()
let _userId = UserUtil.validateUserId(userId: uid)
USER_COLLECTION?.whereField("uid", isEqualTo: _userId).getDocuments(completion: {
(querySnapshot, error) in
if error != nil {
self.error = error
print(error?.localizedDescription as Any)
} else {
for document in querySnapshot!.documents {
userObject = User(snapShot: document)
completionHandler(userObject)
}
}
})
}
Could anyone tell me what I need to change for being able to fit the text data to the correct image in the cell?
With asynchronous calls to fetch user data, the fact that cells are re-used introduces two issues:
When a cell is re-used, make sure that you do not show the values for the prior cell while your asynchronous request is in progress. Either have collectionView(_:cellForItemAt:) reset the values or, better, have the cell’s prepareForReuse make sure the controls are reset.
In the asynchronous request completion handler, check to see if the cell is still visible before updating it. You do this by calling collectionView.cellForItem(at:). If the resulting cell is nil, then the cell is not visible and there's nothing to update.
Thus:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let photoCell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainViewCollectionCell", for: indexPath) as! MainViewCollectionViewCell
// make sure to initialize these so if the cell has been reused, you don't see the old values
photoCell.label.text = nil
photoCell.imageView.image = nil
// now in your asynchronous process completion handler, check to make sure the cell is still visible
someAsynchronousProcess(for: indexPath.row) {
guard let cell = collectionView.cellForItem(at: indexPath) else { return }
// update `cell`, not `photoCell` in here
}
return photoCell
}
Obviously, if one asynchronous completion handler initiates another asynchronous request, then you have to repeat this pattern.

UITableView Floating Cell After Async Fetch

This is in the same vein as a previous question I here
Basically, UITableView cells would occasionally overlap the data underneath them - I tracked that down to reloadRows acting wonky with estimatedHeight, and my solve was to cache the height when calling willDisplay cell: and then return that height, or an arbitrary constant if the row hasn't been seen yet, when calling heightForRow
But now the problem is back! Well, a similar one: after propagating a UITableView with some data, some of it fetched asynchronously, I want to be able to search and repopulate the UITableView.
This data I'm fetching may or may not already be present on the TableView, and in any case I don't consider that - I hit the backend, grab some stuff, and display it.
Except, it gets wonky:
As you can see from the screenshot, there's a cell overlaid on top of another cell with the same content. The TableView only reports there being 2 rows, via numberOfRows, but the View Hierarchy says there are 3 cells present when I click through to the TableView.
Only thing I can figure is there's some weird race condition or interaction that happens when I reloadRow after fetching the openGraph data.
What gives?
Some code:
Search
fileprivate func search(searchText: String, page: Int) {
postsService.searchPosts(searchText: searchText, page: page) { [weak self] posts, error in
if let weakSelf = self {
if let posts = posts, error == nil {
if !posts.isEmpty {
weakSelf.postListController.configureWith(posts: posts, deletionDelegate: nil, forParentView: "Trending")
weakSelf.page = weakSelf.page + 1
weakSelf.scrollTableViewUp()
} else {
// manually add a "No results found" string to tableFooterView
}
} else {
weakSelf.postListController.configureWith(posts: weakSelf.unfilteredPosts, deletionDelegate: nil, forParentView: "Trending")
weakSelf.scrollTableViewUp()
}
}
}
}
**configureWith*
func configureWith(posts: [Post], deletionDelegate: DeletionDelegate?, forParentView: String) {
self.posts = posts
for post in posts {
//some data pre-processing
if some_logic
if rawURLString.contains("twitter") {
let array = rawURLString.components(separatedBy: "/")
let client = TWTRAPIClient()
let tweetID = array[array.count - 1]
client.loadTweet(withID: tweetID, completion: { [weak self] (t, error) in
if let weakSelf = self {
if let tweet = t {
weakSelf.twitterCache.addTweetToCache(tweet: tweet, forID: Int(tweetID)!)
}
}
})
}
openGraphService.fetchOGData(url: rawURL, completion: { [weak self] (og, error) in
weakSelf.openGraphService.fetchOGImageData(url: ogImageURL, completion: { (data, response, error) in
if let imageData = data {
weakSelf.imageURLStringToData[ogImageString] = imageData
weakSelf.queueDispatcher.dispatchToMainQueue {
for cell in weakSelf.tableView.visibleCells {
if (cell as! PostCell).cellPost == post {
let cellIndexPath = IndexPath(row: weakSelf.posts.index(of: post)!, section: 0)
weakSelf.tableView.reloadRows(at: [cellIndexPath], with: UITableViewRowAnimation.automatic)
}
}
}
}
})
})
}
self.deletionDelegate = deletionDelegate
self.parentView = forParentView
queueDispatcher.dispatchToMainQueue { [weak self] in
if let weakSelf = self {
weakSelf.tableView.reloadData()
}
}
scrollToPost()
}

How to know UITableView has finished loading data in swift

I am loading my UITableView using an Arrayin swift. What I want to do is after table has loaded my array should be ampty (want to remove all object in the array then it loads another data set to load another table view)
What I want to do is adding several UItables dinamically to a UIScrollView and load all the data to every UITableView initially. Then user can scroll the scrollview horizontally and view other tables.So in my ViewDidLoadI am doing something like this.
for i in 0..<dm.TableData.count {
self.catID=self.dm.TableData[i]["term_id"] as? String
self.jsonParser()
}
then this is my jsonParser
func jsonParser() {
let urlPath = "http://www.liveat8.lk/mobileapp/news.php?"
let category_id=catID
let catParam="category_id"
let strCatID="\(catParam)=\(category_id)"
let strStartRec:String=String(startRec)
let startRecPAram="start_record_index"
let strStartRecFull="\(startRecPAram)=\(strStartRec)"
let strNumOfRecFull="no_of_records=10"
let fullURL = "\(urlPath)\(strCatID)&\(strStartRecFull)&\(strNumOfRecFull)"
print(fullURL)
guard let endpoint = NSURL(string: fullURL) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(URL:endpoint)
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
print(json)
if let countries_list = json["data"] as? NSArray
{
for (var i = 0; i < countries_list.count ; i++ )
{
if let country_obj = countries_list[i] as? NSDictionary
{
//self.TableData.append(country_obj)
self.commonData.append(country_obj)
}
}
//self.updateUI()
if self.commonData.isEmpty
{
}
else
{
self.updateUI()
}
}
} catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
}
Then UpdateUI()
func updateUI()
{
print("COMMON DATA ARRAY\(self.commonData)")
// print("NEWS DATA ARRAY\(self.newsNews)")
//print("SPORTS DATA ARRAY\(self.sportsNews)")
let tblY:CGFloat=segmentedControl.frame.origin.y+segmentedControl.frame.size.height
tblNews=UITableView.init(frame: CGRectMake(x,0 , self.screenWidth, self.screenHeight-tblY))
tblNews.tag=index
tblNews.delegate=self
tblNews.dataSource=self
tblNews.backgroundColor=UIColor.blueColor()
self.mainScroll.addSubview(tblNews)
x=x+self.screenWidth
index=index+1
tblNews.reloadData()
}
`UITableView` use this `commonData` array as the data source. Now when I scroll table view data load with previous data too.So what is the best way to do this? or else please tell me how can use `self.commonData.removeAll()` after 1 `UITableView` has loaded.Currently I did in `CellforrowAtIndex`
if indexPath.row == self.commonData.count-1
{
self.commonData.removeAll()
}
return cell
but this doesn't solve my problem
You should have separate sets of data, possibly arrays, for each UITableView. iOS will call back to your datasource delegate methods to request data.
It is important that you not delete data from the arrays because iOS is going to call your data source delegate methods expecting data. Even if you display the data in the table views initially, the user may scroll the scroll view causing one of the UITableView's to call your delegate methods to get the data again.
The data source delegate methods, such as func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell have a UITableView parameter that you can use to determine which data source is appropriate.
For example, you might have:
self.commonData1 = ["a", "b", "c"]
self.commonData2 = ["d", "e", "f"]
And you need to keep track of any tables you add to your scroll view:
self.tableView1 = ...the table view you create & add to scroll view
self.tableView2 = ...the table view you create & add to scroll view
And when you're responding to data source calls:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if tableView == self.tableView1 {
return 1
} else if tableView == self.tableView2 {
return 1
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.tableView1 {
return self.commonData1.count
} else if tableView == self.tableView2 {
return self.commonData2.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! IdeaTableViewCell
if tableView == self.tableView1 {
cell.textLabel?.text = self.commonData1[indexPath.row]
} else if tableView == self.tableView2 {
cell.textLabel?.text = self.commonData2[indexPath.row]
}
return cell
}

Resources