iOS - How to init and insert UICollectionViewCell programmatically when data is ready? - ios

I would like to add Ads in my App, like Native Ads or Parallax.
The App is mainly an UICollectionView with many cells displaying different information.
I am using an SDK in order to load the Ad Object.
I have made a custom UICollectionViewCell class in order to init the cell and load the Native Ad as a cell in my UICollectionView.
#objc class AdsCollectionViewCell: BaseCollectionViewCell, AdapterAdsDelegate {
#IBOutlet var outletNativeTitle: UILabel!
#IBOutlet var outletImageNative: UIImageView!
#IBOutlet var outletButtonAction: UIButton!
#IBOutlet var clickableView: UIView!
#IBOutlet var mediaContainer: UIView!
#IBOutlet var bottomView: UIView!
var adFactory = AdsSDKFactory()
var adObject = AdsObject()
#objc func initCell(viewController: HomeViewController) {
DispatchQueue.main.async {
self.isHidden = true
}
// Set some properties for the Ads SDK
adFactory.nativeDelegate = self
adObject.viewController = viewController
adFactory.placementId = "154569"
// method from the SDK to request the Ad
adFactory.loadAds()
}
It is working but my main issue is that I am displaying an empty cell and the content is displayed when I got the delegate response method from the Ad SDK with the Ad Object.
Here is the delegate method I receive in my AdsCollectionViewCell.
func adObjectDidLoad(adView: AdsObject!) {
self.adObject = adView
self.updateCellOutlets() // my method to update labels from the adView
self.setNeedsDisplay()
self.delegate.tileCellDidSucceed!(self)
}
I would like to first init and make the call to the adFactory.loadAds in my HomeViewController, so that when I got the delegate response and the adObject, I don't updateCellOutlets directly but I can self.delegate.tileCellDidSucceed!(self) which is the delegate from my BaseCollectionViewCell in order to notify my HomeViewController that the cell is ready.
How can init a cell outside of the cellForRow method to dynamically insert it when the Ad is loaded ?
EDIT: I have followed the advices and create a Singleton AdsLoader class, that set all the parameters to request the ad, and also make the load request.
I set my HomeViewController as delegate so that I can get the view ad as response directly in my HomeViewController.
My issue now is to insertObject in the collectionView at the right indexPath. Which object should I insert?

I am unable to get a clear picture of the code and specific requirements of your AdSDK. So i will go ahead and write a pseudo code as per my understanding of what needs to be done. Here is what I purpose, instead of calling adFactory.loadAds() into the UICollectionViewCell object, you can perform this task into a different class object. Let say AdLoader
So your add loader should be something like this.
class Adloader {
var adFactory = AdsSDKFactory()
typealias adLoadCompletionHandler = (adView: AdsObject) -> Void
var loadCompletion
func initLoader() {
//Set some properties for the Ads SDK
adFactory.nativeDelegate = self
// method from the SDK to request the Ad
adFactory.loadAds()
}
}
Once the ad is loaded, you can use a completion handler to get update the collection view.
func adObjectDidLoad(adView: AdsObject!) {
adLoadCompletionHandler(adView)
}
So your code can be something like this.
if (needToInsertAd) {
var newAd = Adloader.initLoader()
newAd.adLoadCompletionHandler {
//Code to insert cell at indexpath
}
}
Hope this helps you.

Related

Using UIColorPickerViewController to change the color of cell background color always returns black

I'm developing an app for my school that helps students better understand their grades by getting an analysis on their portfolio of assignments for each class. Right now I am at the point of letting the user create the classes they are in and customize the information within it that is displayed in a tableview with custom cells. The user gives the majority of the information in a child view where they input information such as class name, teacher, grade weighting, etc. I wanted to give the user the ability to change the color of the cell once they are viewing the TableView with all the cells - classes - they made. I decided to go about this by having the cells have a UIButton that they can click on for their determined cell to then pull up a UIColorPickerViewController.
What I wanted to happen was...
User taps button in cell
UIPickerViewController is pulled up
User selects their desired color
User exits UIPickerViewController
Cell is changed to the color
What is actually happening is this
User taps button in cell
Cell background becomes black right as UIPickerViewController is presented
User selects their desired color
User exits UIPickerViewController
Cell remains black
I used a delegate to send the information from the cells and then I used the "colorPickerViewControllerDidFinish()" function and it's still not working out. When I did some debugging I found that the value of the UIColorPickerViewController is actually being stored in the variable I am using, but only after I have already assigned it's value to the cell background so I'm unsure what to do. As you can probably tell, I'm new to swift so apologies for any stupid mistakes in my code.
Custom Cell File
// Protocol for VC's to conform to so they can distinguish which cell has a button being tapped
protocol newlyCreatedCellDelegate: AnyObject
{
func didTapButton(title: String, cellView: UIView)
}
class newlyCreatedClass: UITableViewCell {
// Telling the delegate what to do once they are assigned
weak var delegate: newlyCreatedCellDelegate?
#IBOutlet weak var classContentView: UIView!
#IBOutlet weak var classUIView: UIView!
#IBOutlet weak var classNameLabel: UILabel!
#IBOutlet weak var classTeacherNameLabel: UILabel!
#IBOutlet weak var pointType1NameLabel: UILabel!
#IBOutlet weak var pointType2NameLabel: UILabel!
#IBOutlet weak var pointType3NameLabel: UILabel!
#IBOutlet weak var percent1Label: UILabel!
#IBOutlet weak var percent2Label: UILabel!
#IBOutlet weak var percent3Label: UILabel!
#IBOutlet weak var colorButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
colorButton.layer.cornerRadius = 21
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
// Essentially creating the prep list for the delegate. If they are called - whoever it is - they will go through this 'checklist'
#IBAction func colorButtonTapped(_ sender: Any)
{
delegate?.didTapButton(title: classNameLabel.text!, cellView: classUIView)
}
}
ViewController Extensions
extension ClassSelection: newlyCreatedCellDelegate
{
func didTapButton(title: String, cellView: UIView)
{
let colorPickerVC = UIColorPickerViewController()
colorPickerVC.delegate = self
present(colorPickerVC, animated: true, completion: nil)
colorPickerViewControllerDidFinish(colorPickerVC)
// 'cellBackgroundColor' is a variable declared in the VC to transfer the UIColor value
cellView.backgroundColor = cellBackgroundColor
}
}
extension ClassSelection: UIColorPickerViewControllerDelegate
{
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
cellBackgroundColor = viewController.selectedColor
}
}
You should implement one more UIColorPickerViewControllerDelegate method:
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
cellBackgroundColor = viewController.selectedColor
}
It's a great start! As a really direct answer to your original question:
The reason for your problem is that in your didTapButton function you are presenting the color picker, but then immediately telling the app that the user is done with the picker, and then immediately setting the background color to cellBackgroundColor, which I assume has a default value of UIColor.black.
Instead you should delete those last 2 lines in your didTapButton function - just initialize the picker, set the delegate, and present the picker. Then the delegate method you chose - colorPickerViewControllerDidFinish isn't really the correct one for your purpose. Instead consider using the didSelect delegate method (see docs). When that method is called it will pass you the color the user selected, which you can simply use to set your background color and refresh your tableView cell if needed.
Since you mention you are a new Swift dev I will also mention that UITableView reuses its cells, so simply setting the background color of a cell once will not have the result you are expecting. You will see that as you scroll the cells up and down the colors will change in the various cells, so ultimately you'll need to store the color selections in another way so that each time a cell is being dequeued you can set the correct color based on user input. That part is outside of the scope of the original question, but just letting you know.

Interact with element in UITableView.backgroundView

I have a UITableView which displays some data. If there is no data to display, I show an empty with a button in it. I'm taking advantage of the tableView's backgroundView feature to display my empty state. The problem is that I cannot interact with that button. I assume the tableView disables interaction with the background view. Is there some way to enable it?
You should create strong variable to your background view controller.
class ExampleController: UIViewController {
...
#IBOutlet weak var tableView: UITableView!
private let emptyStateVC = EmptyStateVc(nibName: "EmptyStateVc", bundle: nil)
...
private func setupTableView() {
tableView.backgroundView = emptyStateVC.view
tableView.backgroundView?.isUserInteractionEnabled = true
}
}

How to change value of dictionary in View Controller from UiTableViewCell

So I have TableViewCell's that are being populated from 2 Dicitionaries in my view controller.
var categories : [Int : [String : Any]]!
var assignments : [Int: [String : Any]]!
I have a UiTextField in my cell that the user is supposed to be able to edit. I then want to be able to change the values of certain keys in that dictionary-based off what the user changes and re-display the table with those changes. My main problem is that I don't know how I will be able to access theese variables from within my cell. I have a method in my view controller that takes the row that the text field is in, along with the value of the textField, and updates the dictionaries. What I need is to be able to instantiate the view controller that the cell is in but I need the original instance that already has values loaded into the categories and assignments Dictionaries. If you have any other ideas on how I could accomplish this please post.
You can use delegate for sharing cell-data to your VC:
protocol YourCellDelegate() {
func pickedString(str: String)
}
class YourCell: UITableViewCell {
var delegate: YourCellDelegate! = nil
#IBOutlet weak var textField: UITextField!
.....//some set-method, where you can handle a text
func textHandle() {
guard let del = delegate else { return }
del.pickedString(textField.text)
}
.....
}
And usage in your VC: When you create cell, set its delegate self:
...
cell.delegate = self
...
and sure you VC supported your Delegate Protocol:
class YourVC: UIViewController, YourCellDelegate {
}
And now, you MUST implement protocol method:
class YourVC: UIViewController, YourCellDelegate {
....
func pickedString(str: String) {
}
....
}
All times, when you use textHandle() in your cell, pickedString(str: String) activates in yourVC with string from textField.
Enjoy!

ios Swift ViewController Loaded from Selecting TableView Cell Loses Reference

Full code for this branch here
View controller "MovieDetailsVC" is presented to the navigation controller when a cell is selected.
The presenting view controller, "ViewController", stores the row of the tableView to display in NSUserDefaults as an Int.
"MovieDetailsVC" reads the row ok. It then pulls the whole array of custom class info from CoreData and stores the array row in a property.
The data is displayed ok at first. The IBOutlet connections are setup ok. I've disconnected and reconnected twice all outlets on MovieDetailsVC, so that should be ok.
"viewDidLoad" is called a successive time. Not sure from where. When it is called, the coredata entity and row number are pulled ok.
The issue is at line "lblTitle.text = self.movieRecord.title". I'm assuming any of the IBOutlets would cause the same issue.
The error thrown is what you would see if the outlets were not connected:
fatal error: unexpectedly fond nil while unwrapping Optional value.
code for the MovieDetailsVC is below. Any ideas why this outlet link would break after working ok would be greatly appreciated.
import UIKit
import CoreData
class MovieDetailsVC: UIViewController {
#IBOutlet var lblTitle: UILabel!
#IBOutlet var lblDescr: UILabel!
#IBOutlet var lblLink: UILabel!
#IBOutlet var imgMovie: UIImageView!
var movieRecord:FavMovie!
var favMovies = [FavMovie]()
override func viewDidLoad() {
super.viewDidLoad()
fetchAndSetResult()
}
func fetchAndSetResult() {
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let context = app.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "FavMovie")
do {
let results = try context.executeFetchRequest(fetchRequest)
self.favMovies = results as! [FavMovie]
} catch let err as NSError {
print(err.description)
}
if let row = NSUserDefaults.standardUserDefaults().objectForKey("movieRow") as? Int {
self.movieRecord = self.favMovies[row]
configureCellDescr()
}
}
func configureCellDescr() {
lblTitle.text = self.movieRecord.title
lblDescr.text = self.movieRecord.descrWhyGood
lblLink.text = self.movieRecord.linkImdb
imgMovie.image = self.movieRecord.getImg()
}
}
I just have a look at your source code in github and find the problem. There are two issues and I will explain it following.
it does that the second time that the app overrides viewdidload
The reason that your code would call the viewDidLoad method twice is because you have a segue in your storyboard that connect the tableViewCell to movieDetailVC. This will present the movieDetailVC once you click the cell.
And in your code for didSelectCell method, you create another movieDetailVC object and present it.
So actually movieDetailVC would be presented twice when you click the cell. This cause the issue.
Any ideas why this outlet link would break after working ok would be greatly appreciated
The reason why the IBOutlet is nil is because of the way you present movieDetailVC in your code. You create the movieDetailVC object using: let movieDetailsVC = MovieDetailsVC(). Doing it this way, the IBOutlets will not be connected correctly, because ios dont know about the storyboard information.
The correct way to create a MovieDetailVC object is to instantiate from storyboard. See this post for difference.
Solution
There is a very simple solution for your code design:
just remove let movieDetailsVC = MovieDetailsVC() and self.navigationController?.presentViewController(movieDetailsVC, animated: true, completion: nil) from your ViewController class. Since you save the selection data in NSUserDefault, the cell segue will present movieDetailVC and your movieDetailVC can also get the selection data from userDefault.

(Swift) ViewController's UI-elements can not be edited by implemented delegate method

I want to update the label in the DetailViewController everytime I selected a tableRow in the MasterViewController. To achieve this, I designed a delegate, which I have in the MasterVC
protocol TestTableViewControllerDelegate {
func selectedRow(selectedCar : Car)
}
class TestTableViewController: UITableViewController {
...
var delegate : TestTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = DetailViewController()
The delegate works just fine, (it is implemented correctly in the DetailVC), it can pass values from TestTableVC to DetailVC and also correctly do println(), which prints a new Car.model String to the console every time I select a row in the TTVC.
The DetailVC looks like this (shortened):
class DetailViewController: UIViewController, TestTableViewControllerDelegate {
#IBOutlet var textLabel: UILabel!
var theCar : Car? {
didSet(newCar) {
refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
refreshUI()
}
func selectedRow(selectedCar : Car) {
theCar = selectedCar
refreshUI()
}
func refreshUI() {
textLabel?.text = theCar!.model
}
}
I can achieve any kind of action with my delegate, expect for refreshing the UI. I have tried numerous ways, this is my latest attempt. Before that, I tried setting the textLabel's text property directly within the delegate method, didn't work. This problem only occurs when working with the UI-elements. I know it has something to do with the view not being loaded yet, but why does my refreshUI() function not work at all?
I am still a beginner, so any tip or help would be much appreciated!
A workaround I've used is to cerate a properly in the delegate and pass the value to it instead of the UI element. When the view loads I update the label's text properly with the value of the delegates property. I would think there's a better way to do this (I'm new to programming) but this is the best soultion I've come up with so far. Will update with sample code soon.

Resources