如何通过 urlsession(download/upload 文件)更新 uitableview 单元格中的进度视图

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

在 Objective c 中,我通过 AFNETWORKING 在 uitableviewcell(多个 upload/download)中使用 progressview 编写了 download/upload。并且工作发现它可以更新 progressview/file/cell.

现在我是新手,第一次更改为 SWIFT 编程并使用 urlsession。

代码

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()
    }    
}

?? cell/file 如何在 uitableview 中定义 URLSESSION 更新 progressview 谢谢。

我很好奇您在 Objective-C 中是如何使用 AFNetworking 做到这一点的。至少从概念上讲,在 Swift 中与 URLSession 的实现应该没有太大区别。

恕我直言,关于更新进度的主要问题是,您与所有单元格的单个 UIView 实例共享 progressView 变量。

  1. 您没有为每个单元格初始化一个新的 progressView,而是为所有单元格共享一个视图
  2. 因为 1,cell.addSubview(progressView) 不仅将您的 progressView 添加到该单元格,它还会从其他单元格中删除您的 progressView,因为一个视图只能有一个父视图。
  3. 您的 progressView 将有多个 UIProgressBars 作为子视图。每调用一次 tableView(_:cellForRowAt indexPath:) 一个
  4. self.progressBar.progress = uploadProgress 您将始终更新上次初始化的进度条,因为您没有对其他进度条的引用。

为了让它以干净的方式工作,我建议您对 MVVM 架构进行一些研究。

  1. 为您的单元格创建一个 UITableViewCell 子class
  2. 为该单元格创建一个 ViewModel class
  3. 在 viewController 中为每个单元格存储一个 viewModel 实例(或者在单独的 TableViewDataSource 对象中更好)
  4. 在您的 ViewModel 中实施 URLSessionDelegate 协议并将适当的 viewModel 实例设置为您的 uploadTasks 的委托

快速修复:

删除这些行:

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

添加变量:

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

添加辅助函数来计算viewTag:

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

将您的 tableView(_:cellForRowAt indexPath:) 更改为:

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
}

将您的 uploadImage 方法更改为:

@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()
}

将您的 urlSession 委托方法更改为:

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