簡體   English   中英

如何通過 urlsession 更新 uitableview 單元格中的進度視圖(下載/上傳文件)

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

在 Objective ci 中,AFNETWORKING 在 uitableviewcell 中使用進度視圖編寫了下載/上傳(多次上傳/下載)。 並發現它可以是更新進度視圖/文件/單元格。

現在我是菜鳥第一次改用 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()
    }    
}

?? 如何通過單元格/文件在 uitableview 中定義 URLSESSION 更新進度視圖 謝謝。

我很好奇你是如何使用 AFNetworking 在 Objective-C 中做到的。 至少在概念上,使用 URLSession 在 Swift 中實現應該沒有太大區別。

恕我直言,您關於更新進度的主要問題是,您與所有單元格的單個 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 子類
  2. 為該單元格創建一個 ViewModel 類
  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
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM