update progress view download within custom table view cell in swift language - swift3

i have an question about how can i update my progress view in my custom table view cell which is download file from the internet ? if i reload the table view it take huge memory and increasing the memory
tableView.reloadData()
also if i reload the only one section it also take huge memory and increase memory
tableView.reloadSections([0], with: .none)
so please what is the best way to update progress view within my custom table view cell?
the following my custom table view class :
import UIKit
class DownloadingTableViewCell: UITableViewCell {
#IBOutlet weak var movieDeleteButton: UIButton!
#IBOutlet weak var movieImageView: UIImageView!
#IBOutlet weak var movieNameLabel: UILabel!
#IBOutlet weak var movieProgreesView: UIProgressView!
#IBOutlet weak var movieSizeLabel: UILabel!
weak var movieObject:DownloadVideo? {
didSet {
updateUI()
}
}
func updateUI() {
movieImageView.image = nil
movieNameLabel.text = nil
movieSizeLabel.text = nil
movieProgreesView.progress = 0.0
if let movieObject = movieObject {
movieImageView.image = UIImage(named: "movie")
movieNameLabel.text = movieObject.videoName
// set the label size file
if movieObject.totalData != nil {
let totalSize = ByteCountFormatter.string(fromByteCount:movieObject.totalData!, countStyle: .binary)
movieSizeLabel.text = String(format: "%.1f%% of %#", (movieObject.data)! * 100 ,totalSize)
}
/////////////////////////
if let movieData = movieObject.data {
movieProgreesView.progress = movieData
}
}// end the fetch object
}
}
thanks a lot

You should add a parameter in your data source related to file downloading progress and update progress in main thread
if let movieData = movieObject.data {
dispatch_get_main_queue().asynchronously() {
movieProgreesView.progress = movieData
}
}

Related

Prevent reflow/redraw every time #State is changed

didSelectItemAt causes UI to reflow/redraw every time value for lastSelectedIndex is changed, causing performance issue. I'm not sure if I have used #State properly to propagate value from child to parent.
P.S. I need to use UICollectionView for a reason instead of swiftui List or ScrollView.
import Foundation
import SwiftUI
struct ContentView: View {
#State var lastSelectedIndex : Int = -1
var body: some View {
ZStack {
CustomCollectionView(lastSelectedIndex: $lastSelectedIndex)
Text("Current Selected Index \(lastSelectedIndex)")
}
}
}
struct CustomCollectionView: UIViewRepresentable {
#Binding var lastSelectedIndex : Int
func makeUIView(context: Context) -> UICollectionView {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = CGSize(width: 400, height: 300)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.reuseId)
collectionView.delegate = context.coordinator
collectionView.dataSource = context.coordinator
collectionView.backgroundColor = .systemBackground
collectionView.isDirectionalLockEnabled = true
collectionView.backgroundColor = UIColor.black
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.alwaysBounceVertical = false
return collectionView
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
uiView.reloadData()
}
func makeCoordinator() -> CustomCoordinator {
CustomCoordinator(self)
}
}
class CustomCoordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {
let parent:CustomCollectionView
init(_ parent:CustomCollectionView) {
self.parent = parent
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.reuseId, for: indexPath) as! CustomCollectionViewCell
cell.backgroundColor = UIColor.red
cell.label.text = "Current Index is \(indexPath.row)"
NSLog("Called for Index \(indexPath.row)")
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
parent.lastSelectedIndex = indexPath.row
}
}
class CustomCollectionViewCell: UICollectionViewCell {
static let reuseId = "customCell"
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
label.numberOfLines = 0
contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Quick fix
You can use an ObservableObject for this purpose
class Model: ObservableObject {
#Published var index: Int
init(index: Int) { self.index = index }
}
Create a container view for your text field that will update when this model changes:
struct IndexPreviewer: View {
#ObservedObject var model: Model
var body: Text { Text("Current Selected Index \(model.index)") }
}
Then include this model and the observer in your ContentView:
struct ContentView: View {
private let model = Model(index: -1)
var body: some View {
VStack {
IndexPreviewer(model: model)
CustomCollectionView(lastSelectedIndex: index)
}
}
var index: Binding<Int> {
Binding {model.index} set: {model.index = $0}
}
}
Explanation
The problem is that once you update a #State property, the containing view's body will be re-evaluated. So you cannot create a #State property on the view that contains your collection view, because each time you select a different cell, a message will be sent to your container who will re-evaluate it's body that contains the collection view. Hence the collection view will refresh and reload your data like Asperi wrote in his answer.
So what can you do to resolve that? Remove the state property wrapper from your container view. Because when you update the lastSelectedIndex, your container view (ContentView) should not be rendered again. But your Text view should be updated. So you should wrap your Text view in a separate view that observes the selection index.
This is where ObservableObject comes in to play. It is a class that can store State data on itself instead of being stored directly in a property of a view.
So why does IndexPreviewer update when the model changes and ContentView not, you might ask? That is because of the #ObservedObject property wrapper. Adding this to a view will refresh the view when the associated ObservableObject changes. That is why we do not include #ObservedObject inside ContentView but we do include it in IndexPreviewer.
How/Where to store your models?
For the sake of simplicity I added the model as a constant property to ContentView. This is however not a good idea when ContentView is not the root view of your SwiftUI hierarchy.
Say for example that your content view also receives a Bool value from its parent:
struct Wrapper: View {
#State var toggle = false
var body: some View {
VStack {
Toggle("toggle", isOn: $toggle)
ContentView(toggle: toggle)
}
}
}
struct ContentView: View {
let toggle: Bool
private let model = Model(index: -1)
...
}
When you run that on iOS 13 or 14 and try to click on collection view cell and then change the toggle, you will see that the selected index will reset to -1 when you change the toggle. Why does this happen?
When you click on the toggle, the #State var toggle will change and since it uses the #State property wrapper the body of the view will be recomputed. So another ContentView will be constructed and with it, also a new Model object.
There are two ways to prevent this from happening. One way is to move your model up in the hierarchy. But this can create a cluttered root view at the top of your view hierarchy. In some cases it is better to leave transient UI state local to your containing UI component. This can be achieved by an undocumented trick which is to use #State for your model objects. Like I said, it is currently (july 2020) undocumented but properties wrapped using #State will persist their value accross UI updates.
So to make a long story short: You should probably be storing your model using:
struct ContentView: View {
#State private var model = Model(index: -1)
It is a feature of #State to cause dependent view refresh. In case of representable changing dependent state calls updateUIView, so, as you put reloadData in it - its reloaded:
func updateUIView(_ uiView: UICollectionView, context: Context) {
// Either remove reload from here (ig. make it once in makeUIView to load
// content, or make reload here only conditionally depending on some parameter
// which really needs collection to be reloaded
// uiView.reloadData()
}

Custom delegate is always nil

I have a problem with my delegate use in a protocol, many person face the same problem but no answer works for me.
My first class is FavorisHeaderTableView
import UIKit
protocol FavorisHeaderDelegate {
func changeFavoris(sender: FavorisHeaderTableViewCell)
}
class FavorisHeaderTableViewCell: UITableViewCell {
#IBOutlet weak var lblTitle: UILabel!
#IBOutlet weak var txtFavoriteNameInput: UITextField!
#IBOutlet weak var lblIcon: UILabel!
#IBOutlet weak var lblTime: UILabel!
#IBOutlet weak var lblDescription: UILabel!
#IBOutlet weak var btnFavHeart: UIButton!
#IBOutlet weak var btnFavHome: UIButton!
#IBOutlet weak var btnFavShop: UIButton!
#IBOutlet weak var btnFavWork: UIButton!
#IBOutlet weak var btnFavGolf: UIButton!
var myDelegate: FavorisHeaderDelegate? = nil
var defaultIcon:FavIconType = .heart
var selectedIcon:UIButton? = nil {
didSet {
selectedIcon!.backgroundColor = Design.Palette.red
selectedIcon?.layer.cornerRadius = 3
selectedIcon?.tintColor = Design.Palette.white
}
willSet {
if selectedIcon != nil {
selectedIcon!.backgroundColor = UIColor.clear
selectedIcon?.tintColor = UIColor(red:0.671, green:0.651, blue:0.635, alpha:1)
}
}
}
#IBAction func didSelectIcon(_ sender: UIButton) {
selectedIcon = sender
self.myDelegate?.changeFavoris(sender: self)
}
#IBAction func changeTitle(_ sender: Any) {
txtFavoriteNameInput.text = "gare centro"
print("delegate: ",myDelegate)
if myDelegate != nil {
myDelegate?.changeFavoris(sender: self)
}
}
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
}
}
The second class who use the protocol is
addFavoriteViewController: UIViewController {
// MARK properties
let defaultLocalizer = AMPLocalizeUtils.defaultLocalizer
var favorisName:String? = nil
var defaultIcon:FavIconType = .heart
var delegate:handleFavorite? = nil
#IBOutlet weak var favTableView: UITableView!
var headerCell:FavorisHeaderTableViewCell?
override func viewDidLoad() {
super.viewDidLoad()
// Localization of labels
//lblAddToFavorite.text = defaultLocalizer.stringForKey(key: "str_favorites_addTitle")
//lblFavoriteName.text = defaultLocalizer.stringForKey(key: "str_favorites_nameInput")
favTableView.delegate = self
favTableView.dataSource = self
self.headerCell?.myDelegate = self
}
override func viewWillAppear(_ animated: Bool) {
// Text Field
//favorisName.clearButtonMode = .whileEditing
}
var place:PlaceModel? = nil
var itinerary:(source:PlaceModel, destination:PlaceModel)? = nil
let db = DataController.shared
var favorite:FavoritesMO? = nil
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension UGOaddFavoriteViewController: FavorisHeaderDelegate {
func changeFavoris(sender: FavorisHeaderTableViewCell) {
defaultIcon = sender.defaultIcon
favorisName = sender.txtFavoriteNameInput.text
}
}
When I try this code "myDelegate" is always nil and I don't understand what's wrong despite of all topic read about this problem.
You are setting self as the delegate of the wrong cell!
Here:
self.headerCell?.myDelegate = self
you set the headerCell's delegate to self, but headerCell is never actually displayed on the screen!
You need to actually set the delegates of the cells on the screen, not the delegate of a random cell that you created.
The best place to do this is cellForRowAtIndexPath:
let cell = tableView.dequeueResusableCell(withIdentifier:...)!
// configure your cell
cell.delegate = self // set the delegate here!

How do I automate the position of a UIViewController container's member view?

Scenario:
1. UIViewContainer having one (1) child UIViewController.
2. I'm using a UIStoryboard.
Goal:
To animate the entrance of the child UIViewController's view from the left edge of the container (like a UIActionSheet).
I have initially set the member view to blue.
Problem: I can correctly animate the physical coordinates but not the width constraint (nor any other constraint).
Here's my code:
import UIKit
class HamburgerContainerViewController: UIViewController {
#IBOutlet weak var containerView: UIView!
var memberView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
memberView = containerView!.subviews.first!
}
override func viewDidLayoutSubviews() {
memberView.backgroundColor = UIColor.blue
memberView.frame = CGRect(x: 0, y: 0, width: 100, height: 400)
return
}
#IBAction func DoSomething(_ sender: Any) {
memberView.translatesAutoresizingMaskIntoConstraints = false
UIView.animate(withDuration: 1.0) {
// self.memberView.frame.size.width = 345.0 // ... this works.
self.memberView.widthAnchor.constraint(equalToConstant: 25.0) // ...this makes the view slide somewhere else.
self.view.layoutIfNeeded()
}
}
}
Running my code causes the blue member view to side to the upper left of the screen leaving its UIButton & UILabel in its wake.
Here's the storyboard:
FYI: Here's a list of constraints upon the member UIViewController's view:
In answer to your question, you could either animate the constraints of the container view itself, or put a subview within the child view controller's view and animate that. But don't try to animate the frame of the child view controller's root view if that view was created and managed by IB's "container view".
So I added the view to be resized as a subview of the child view controller's view, and then I could resize that. For example, add two constraints (one that is H:[expandingView]| (which I hooked up to an #IBOutlet for wideConstraint and another that is H:[expandingView(100#750)] that I hooked up to and #IBOutlet called narrowConstraint. Then the child view controller can do:
class ChildViewController: UIViewController {
#IBOutlet var narrowConstraint: NSLayoutConstraint!
#IBOutlet var wideConstraint: NSLayoutConstraint!
#IBOutlet weak var expandingView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
wideConstraint.isActive = false // disable the wide one when the child is first loaded
}
// when I want to, I just toggle the `isActive` for the two constraints
func toggle() {
narrowConstraint.isActive = !narrowConstraint.isActive
wideConstraint.isActive = !wideConstraint.isActive
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
}
Then the parent can tell the child to toggle the size of the expandingView when the button is tapped:
#IBAction func didTapButton(_ sender: Any) {
if let child = childViewControllers.first as? ChildViewController {
child.toggle()
}
}
That yields:

IOS 10 - Swift 3.0: Stop the delegated function to save the image

I'm making an App with swift 3, xCode 8.2 and IOS10. The interface has a thermal vision screen (FLIR ONE). My question is, how can I take a photo? My code is:
import UIKit
class ThermalCameraVC: UIViewController, FLIROneSDKImageReceiverDelegate, FLIROneSDKStreamManagerDelegate {
//MARK: OUTLETS
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var labelStatusCamera: UITextField!
#IBOutlet weak var labelCargeCamera: UITextField!
#IBOutlet weak var icnCancelPicture: UIButton!
#IBOutlet weak var icnUploadPicture: UIButton!
#IBOutlet weak var iconTakePicture: UIButton!
//MARK: VARIABLES
var simulatorStatus = false
var cameraBussy = false
override func viewDidLoad() {
super.viewDidLoad()
FLIROneSDKStreamManager.sharedInstance().addDelegate(self)
FLIROneSDKStreamManager.sharedInstance().imageOptions = FLIROneSDKImageOptions(rawValue: FLIROneSDKImageOptions.blendedMSXRGBA8888Image.rawValue)!
icnCancelPicture.isHidden = true
icnUploadPicture.isHidden = true
}
//MARK: CANCEL PICTURE
#IBAction func cancelPicture(_ sender: Any) {
cameraBussy = false
}
//MARK: UPLOAD PICTURE AMAZON S3
#IBAction func uploadPicture(_ sender: Any) {
}
func flirOneSDKDelegateManager(_ delegateManager: FLIROneSDKDelegateManager!, didReceiveBlendedMSXRGBA8888Image msxImage: Data!, imageSize size: CGSize){
let image = FLIROneSDKUIImage(format: FLIROneSDKImageOptions.blendedMSXRGBA8888Image, andData: msxImage, andSize: size)
//HERE I NEED TO STOP THE DELEGATED FUNCTION TO SAVE THE IMAGE !!
if self.cameraBussy{
//cameraBussy = false
}else{
DispatchQueue.main.async{
self.imageView.image = image
}
}
}
#IBAction func takeThermalPicture(_ sender: Any) {
cameraBussy = true
icnCancelPicture.isHidden = false
icnUploadPicture.isHidden = false
iconTakePicture.isHidden = true
}
}
How can I stop the flow of data in this delegated function? Because it is continually calling.
Set the delegate value to nil.
#IBAction func takeThermalPicture(_ sender: Any) {
FLIROneSDKStreamManager.sharedInstance().addDelegate(nil)
cameraBussy = true
icnCancelPicture.isHidden = false
icnUploadPicture.isHidden = false
iconTakePicture.isHidden = true
}

SWIFT : Display label

I begin to learn swift language and i've a problem.
I try to display a label when i put the button. I show you :
#IBAction func nextRule(sender : UIButton) {
rule = Rules(game: Int(randomNumber))
}
#IBOutlet var lTitle: UILabel!
#IBOutlet var lRule: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
lTitle.text = rule.title
lRule.text = rule.rule
Rules(game: Int) contain a model whith "title" And "rule"
When i press the button, i want to display a new Rule, is it possible by doing something like his?
Thanks for your answer.
How about assign your rule to lTitle and lRule like this
#IBAction func nextRule(sender : UIButton) {
rule = Rules(game: Int(randomNumber))
lTitle.text = rule.title
lRule.text = rule.rule
}