How to update progressview in uitableview cell by urlsession (download/upload file)

我只是一个虾纸丫 提交于 2021-02-11 16:52:59

问题


In Objective c i wrote download/upload with progressview in uitableviewcell (multiple upload/download) by AFNETWORKING. and work find it's can be update progressview/file/cell.

and Now im noob first time change to SWIFT programming and use urlsession.

code

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {

    //var dataArr:Dictionary<String,String> = [:]
    var dataArr : NSMutableArray = NSMutableArray.init()
    var myTableview:UITableView = UITableView.init()
    let color = UIColor(red: 69/255, green: 57/255, blue: 169/255, alpha: 1.0)
    let cellID: String = "customCell"
    var progressBar : UIProgressView = UIProgressView.init()
    let progressView : UIView = UIView.init(frame: CGRect(x: 100, y: 10, width: 100, height: 20))

    func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
    {
        let uploadProgress:Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
        print(uploadProgress)

        DispatchQueue.main.async {

            self.progressBar.progress = uploadProgress
        }
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataArr.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath)

        let textName: UILabel = UILabel.init(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
        textName.textColor=UIColor.black
        textName.backgroundColor=UIColor.green
        textName.text=dataArr[indexPath.row] as? String
        print("\(dataArr[indexPath.row])");
        textName.font=UIFont.systemFont(ofSize: 14)
        cell.addSubview(textName)


        progressView.backgroundColor = UIColor.red
        progressView.tag=indexPath.row

        let customKeys=["type","Facebook","Google","Twitter"];
        let customsValues=["uploadFile","Mark","Lary","Goo"];
        let customDatas=Dictionary(uniqueKeysWithValues: zip(customKeys,customsValues))


        progressBar = UIProgressView.init(frame: CGRect(x: 0, y: 5, width: 100, height: 20))
        progressBar.tag=indexPath.row
        progressView.addSubview(progressBar)

        cell.addSubview(progressView)

        uploadImage(data_dict: customDatas, uploadImg: dataArr[indexPath.row] as! String)



        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

        let hCell:CGFloat = 50.0

        return hCell
    }

    //    override func viewWillAppear(_ animated: Bool) {
    //        super.viewWillAppear(animated)
    //        setNeedsStatusBarAppearanceUpdate()
    //    }
    override var preferredStatusBarStyle: UIStatusBarStyle {
        // Change font of status bar is white.
        .lightContent

    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        dataArr=["1.jpg","2.jpg","3.jpg"]
        //print(dataArr)
        let myScreen = UIScreen.main.bounds
        let statusHieght = UIApplication.shared.statusBarFrame.height

        if #available(iOS 13.0, *) {
            let app = UIApplication.shared
            let statusBarHeight: CGFloat = app.statusBarFrame.size.height

            let statusbarView = UIView()
            statusbarView.backgroundColor = color
            statusbarView.tintColor = .white
            view.addSubview(statusbarView)


            statusbarView.translatesAutoresizingMaskIntoConstraints = false
            statusbarView.heightAnchor
                .constraint(equalToConstant: statusBarHeight).isActive = true
            statusbarView.widthAnchor
                .constraint(equalTo: view.widthAnchor, multiplier: 1.0).isActive = true
            statusbarView.topAnchor
                .constraint(equalTo: view.topAnchor).isActive = true
            statusbarView.centerXAnchor
                .constraint(equalTo: view.centerXAnchor).isActive = true

        } else {
            let statusBar = UIApplication.shared.value(forKeyPath: "statusBarWindow.statusBar") as? UIView
            statusBar?.backgroundColor = color
        }

        UINavigationBar.appearance().barTintColor = color
        UINavigationBar.appearance().tintColor = .white
        UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
        UINavigationBar.appearance().isTranslucent = false

        let navBar = UINavigationBar(frame: CGRect(x: 0, y: statusHieght, width: myScreen.size.width, height: 44))

        //navBar.isTranslucent=true
        //navBar.backgroundColor = .red
        let navItem = UINavigationItem(title: "SomeTitle")
        let doneItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, target:nil , action:#selector(ClickDone))
        navItem.rightBarButtonItem = doneItem
        navBar.setItems([navItem], animated: false)
        self.view.addSubview(navBar)

        let AllTopDistance=statusHieght+navBar.frame.size.height

        let myView:UIView = UIView.init(frame: CGRect(x: 0, y: AllTopDistance, width: myScreen.size.width, height: myScreen.size.height-AllTopDistance))
        myView.backgroundColor = .lightGray
        myTableview=UITableView.init(frame: CGRect(x: 0, y: 0, width: myScreen.size.width, height: myScreen.size.height-AllTopDistance))
        myTableview.register(UITableViewCell.self, forCellReuseIdentifier: cellID)

        print("\(statusHieght) \(myScreen.size.width) \(AllTopDistance)")
        myTableview.delegate=self
        myTableview.dataSource=self
        myTableview.backgroundColor=UIColor.red

        myView.addSubview(myTableview)

        self.view.addSubview(myView)
    }

    @objc func ClickDone(){
        print("Done")
    }


    func calculateTopDistance() -> CGFloat{

        /// Create view for misure
        let misureView : UIView     = UIView()
        misureView.backgroundColor  = .clear
        view.addSubview(misureView)

        /// Add needed constraint
        misureView.translatesAutoresizingMaskIntoConstraints                    = false
        misureView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive     = true
        misureView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive   = true
        misureView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        if let nav = navigationController {
            misureView.topAnchor.constraint(equalTo: nav.navigationBar.bottomAnchor).isActive = true
        }else{
            misureView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        }

        /// Force layout
        view.layoutIfNeeded()

        /// Calculate distance
        let distance = view.frame.size.height - misureView.frame.size.height

        /// Remove from superview
        misureView.removeFromSuperview()

        return distance

    }


    @objc func uploadImage(data_dict : Dictionary<String,String>, uploadImg : String)
    {
        print("click \(data_dict)")
        let image = UIImage(named: uploadImg)

        // generate boundary string using a unique per-app string
        let boundary = UUID().uuidString


        let config = URLSessionConfiguration.default
        //let session = URLSession(configuration: config)

        let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)

        // Set the URLRequest to POST and to the specified URL
        var urlRequest = URLRequest(url: URL(string: "http://x.x.x.x/xxx/Labs.php")!)
        urlRequest.httpMethod = "POST"

        // Set Content-Type Header to multipart/form-data, this is equivalent to submitting form data with file upload in a web browser
        // And the boundary is also set here
        urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

        var data = Data()

        for (key, value) in data_dict {
            print(key, value)
            let fieldName = key
            let fieldValue = value
            data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
            data.append("Content-Disposition: form-data; name=\"\(fieldName)\"\r\n\r\n".data(using: .utf8)!)
            data.append("\(fieldValue)".data(using: .utf8)!)

        }

        // Add the image data to the raw http request data
        data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
        data.append("Content-Disposition: form-data; name=\"fileToUpload\"; filename=\"\(uploadImg)\"\r\n".data(using: .utf8)!)
        data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
        data.append((image?.jpegData(compressionQuality: 1.0))!)

        // End the raw http request data, note that there is 2 extra dash ("-") at the end, this is to indicate the end of the data
        // According to the HTTP 1.1 specification https://tools.ietf.org/html/rfc7230
        data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)

        // Send a POST request to the URL, with the data we created earlier
        session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in

            if(error != nil){
                print("\(error!.localizedDescription)")
            }

            guard let responseData = responseData else {
                print("no response data")
                return
            }

            if let responseString = String(data: responseData, encoding: .utf8) {
                print("Response data : \(responseString)")
            }
        }).resume()
    }    
}

?? How define URLSESSION update progressview in uitableview by cell/file Thank you.


回答1:


I'm curious how you did it in Objective-C with AFNetworking. At least conceptional, there shouldn't be a big difference to implementation in Swift with URLSession.

Imho your main problem about updating your progress is, that you share the progressView variable with a single UIView instance for all of your cells.

  1. you do not initialize a new progressView for each cell, but share one view for all cells
  2. because of 1, cell.addSubview(progressView) doesn't only add your progressView to that cell, it removes your progressView from the other cells as well, because a view can only have one parent view.
  3. your progressView will have multiple UIProgressBars as subview. One for each time tableView(_:cellForRowAt indexPath:) is called
  4. with self.progressBar.progress = uploadProgress you will always update the progressBar which was last initialized, because you don't have a reference to the other ones.

To get this to work in a clean way, I'd recommend you do some research to MVVM architecture.

  1. create a UITableViewCell subclass for your cell
  2. create a ViewModel class for that cell
  3. store a viewModel instance for each of your cells in your viewController (or better in a separate TableViewDataSource object)
  4. implement URLSessionDelegate protocol in your ViewModel and set the appropriate viewModel instance as delegate for your uploadTasks

For a quick and dirty fix:

Remove these lines:

var progressBar : UIProgressView = UIProgressView.init()
let progressView : UIView = UIView.init(frame: CGRect(x: 100, y: 10, width: 100, height: 20))  

Add variable:

var uploadTasks: [URLSessionDataTask: IndexPath] = [:]

Add a helper function to calculate viewTag:

func viewTag(for indexPath: IndexPath) -> Int {
    return indexPath.row + 1000
}

Change your tableView(_:cellForRowAt indexPath:) to:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath)

    let textName: UILabel = UILabel.init(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
    textName.textColor=UIColor.black
    textName.backgroundColor=UIColor.green
    textName.text=dataArr[indexPath.row] as? String
    print("\(dataArr[indexPath.row])");
    textName.font=UIFont.systemFont(ofSize: 14)
    cell.addSubview(textName)

    let progressView = UIView(frame: CGRect(x: 100, y: 10, width: 100, height: 20))
    progressView.backgroundColor = UIColor.red

    let customKeys=["type","Facebook","Google","Twitter"];
    let customsValues=["uploadFile","Mark","Lary","Goo"];
    let customDatas=Dictionary(uniqueKeysWithValues: zip(customKeys,customsValues))


    let progressBar = UIProgressView.init(frame: CGRect(x: 0, y: 5, width: 100, height: 20))
    progressBar.tag = viewTag(for: indexPath)
    progressView.addSubview(progressBar)

    cell.addSubview(progressView)

    uploadImage(data_dict: customDatas, indexPath: indexPath)

    return cell
}

Change your uploadImage method to:

@objc func uploadImage(data_dict : Dictionary<String,String>, indexPath : IndexPath) {
    print("click \(data_dict)")
    let uploadImg = dataArr[indexPath.row] as! String
    let image = UIImage(named: uploadImg)

    ...

    let task = session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
        ...
    })

    uploadTasks[task] = indexPath
    task.resume()
}

Change your urlSession delegate method to:

func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
    let uploadProgress:Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
    print(uploadProgress)

    guard let indexPath = uploadTasks[task] else { return }
    let viewTag = viewTag(for: indexPath)
    guard let progressBar = self.view.viewWithTag(viewTag) as? UIProgressView else { return }

    DispatchQueue.main.async {
        progressBar.progress = uploadProgress
    }
}


来源:https://stackoverflow.com/questions/60200140/how-to-update-progressview-in-uitableview-cell-by-urlsession-download-upload-fi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!