You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中使用Delegate传递UIImage至ViewController遇阻求助

如何在Tabman标签页间通过Delegate传递UIImage?

更新
之前传递UIImage的方式有误,因为无法确定接收UIImage数据的ViewController实例。我没有使用协议,而是通过AppDelegate.swift共享UIImage数组,这种方式运行正常。

我是Swift新手,需要实现以下功能:

  • 使用Tabman库,有两个标签页视图控制器:FirstTabViewController和SecondTabViewController
  • FirstTabViewController上有一个UIImageView和两个UIButton
  • SecondTabViewController上有一个UICollectionView用于展示图片
  • 点击“Get an Image”按钮时,从The Dog API服务器下载图片并显示在UIImageView中
  • 点击“Save an Image”按钮时,Delegate应被触发,但实际没生效——SecondTabViewController的collectionView看不到图片,控制台也没打印Delegate触发的测试信息,不知道怎么修复
  • 我试过通过AppDelegate保存ViewController实例,也搜过指定Delegate对应ViewController实例的方法,有答案建议用segue/prepare(),但因为用了Tabman,内置Segue操作起来很复杂
  • 想找无需创建新segue,通过Delegate传递UIImage数据的方法

相关代码

1. Delegate协议代码

import UIKit

protocol ImageDelegate {
    func sendImage(with image: UIImage)
}

2. TabManViewController代码

该视图控制器用于声明Tabman的标签页视图,并将视图控制器实例保存到AppDelegate的属性中

import Foundation
import Tabman
import Pageboy

class TabManViewController: TabmanViewController {
    private var viewControllers: Array<UIViewController> = []
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let firstVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FirstTabVC") as! FirstTabViewController
        let secondVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondTabVC") as! SecondTabViewController
        
        viewControllers.append(firstVC)
        viewControllers.append(secondVC)
        
        let ad = UIApplication.shared.delegate as? AppDelegate
        
        ad?.firstVCInstance = firstVC
        ad?.secondVCInstance = secondVC
        
        self.dataSource = self
        
        // Create bar
        let bar = TMBar.ButtonBar()
        bar.layout.transitionStyle = .snap // Customize
        bar.indicator.weight = .custom(value: 1)
        bar.indicator.tintColor = .black
        // tap center
        bar.layout.alignment = .centerDistributed
        // tap 사이 간격
        bar.layout.interButtonSpacing = 12
        // tap 선택 / 미선택
        bar.buttons.customize { (button) in
            button.tintColor = .gray
            button.selectedTintColor = .black
            button.selectedFont = UIFont.systemFont(ofSize: 16, weight: .medium)
        }
        
        // Add to view
        addBar(bar, dataSource: self, at: .top)
    }
}

//MARK: - Tabman DataSource
extension TabManViewController: PageboyViewControllerDataSource, TMBarDataSource {
    func barItem(for bar: TMBar, at index: Int) -> TMBarItemable {
        let item = TMBarItem(title: "")
        if index == 0 {
            item.title = "Get a dog"
        } else if index == 1 {
            item.title = "Gallery"
        } else {
            item.title = "Page \(index+1)"
        }
        item.image = UIImage(named: "image.png") // ??
        
        return item
    }
    
    func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int {
        return viewControllers.count
    }
    
    func viewController(for pageboyViewController: PageboyViewController,
                        at index: PageboyViewController.PageIndex) -> UIViewController? {
        return viewControllers[index]
    }
    
    func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
        return nil
    }
}

3. FirstTabViewController代码

import Tabman
import UIKit
import Alamofire

class FirstTabViewController: TabmanViewController {    
    var delegate: ImageDelegate?
    var dogImage: UIImage?
    
    @IBOutlet weak var imageView: UIImageView!
    @IBAction func GetImagePressed(_ sender: UIButton) {
        getADog()
    }
    @IBAction func SaveImagePressed(_ sender: UIButton) {
        print("==")
        print("SaveImage Btn Pressed")
        if let safeImage = dogImage {
            delegate?.sendImage(with: safeImage)
        } else {
            print("dogImage is nil")
        }
    }
    private var apiKey: String {
        get {
            guard let filepath = Bundle.main.path(forResource: "Keys", ofType: "plist") else {
                fatalError("Couldn't find file 'Keys.plist'.")
            }
            let plist = NSDictionary(contentsOfFile: filepath)
            guard let value = plist?.object(forKey: "API_KEY") as? String else {
                fatalError("Couldn't find key 'API_KEY' in 'Keys.plist'.")
            }
            return value
        }
    }
    
    //MARK: - View Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

4. SecondTabViewController代码

import Foundation
import Tabman

class SecondTabViewController: TabmanViewController {
    @IBOutlet weak var collectionView: UICollectionView!
    
    var collectedDogImages = [UIImage]()
    let sectionInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    
    override func viewDidLoad() {
        collectionView.dataSource = self
        collectionView.delegate = self
        
        let ad = UIApplication.shared.delegate as? AppDelegate
        if let firstVCInstance = ad?.firstVCInstance {
            firstVCInstance.delegate = self
        } else {
            print("Error: Can't read firstVCInstance from Appdelegate.")
        }

    }
}

//MARK: - CollectionView
extension SecondTabViewController: UICollectionViewDataSource,
                                   UICollectionViewDelegate,
                                   UICollectionViewDelegateFlowLayout
{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        print("cell count = \(collectedDogImages.count)") // It says always 0
        return collectedDogImages.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? UICollectionViewCell
        else {
            return UICollectionViewCell()
        }
        let imageData = collectedDogImages[indexPath.row - 1]
        let appendingView = UIView(frame: CGRect.init())
        let appendingImageView = UIImageView(image: imageData)
        appendingView.addSubview(appendingImageView)

        cell.contentView.addSubview(appendingView)
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = collectionView.frame.width
        let height = collectionView.frame.height
        let itemsPerRow: CGFloat = 2
        let widthPadding = sectionInsets.left * (itemsPerRow + 1)
        let itemsPerColumn: CGFloat = 3
        let heightPadding = sectionInsets.top * (itemsPerColumn + 1)
        let cellWidth = (width - widthPadding) / itemsPerRow
        let cellHeight = (height - heightPadding) / itemsPerColumn
        
        return CGSize(width: cellWidth, height: cellHeight)
    }
    
}

//MARK: - InsertImageDelegate
extension SecondTabViewController: ImageDelegate {
    func sendImage(with image: UIImage) {
        //test
        print("=")
        print("secondTab delegate method activated") // Never printed on console
        
        self.collectedDogImages.append(image)
        let itemLocation = self.collectedDogImages.count
        let indexPaths = IndexPath(item: itemLocation, section: 0)
        
        self.collectionView.insertItems(at: [indexPaths])
    }
}

解决方案

问题根源

你的Delegate未生效核心有两个原因:

  1. 内容控制器继承错误:FirstTab和SecondTab继承了TabmanViewController,但它们只是普通标签页内容,应该继承UIViewController,错误继承会导致实例管理和生命周期异常。
  2. Delegate赋值逻辑冗余且不可靠:通过AppDelegate中转实例容易出现引用问题,且在Tabman初始化时直接关联两个控制器更稳妥。
  3. CollectionView索引和视图布局错误:索引越界、ImageView未设置frame导致无法显示。

修复步骤

1. 修正内容控制器的继承类

将两个标签页控制器的父类从TabmanViewController改为UIViewController:

// FirstTabViewController.swift
class FirstTabViewController: UIViewController {    
    // 原有代码不变
}

// SecondTabViewController.swift
class SecondTabViewController: UIViewController {
    // 原有代码不变
}

2. 在TabmanViewController中直接设置Delegate

删除AppDelegate相关代码,直接在创建子控制器时完成Delegate绑定:

// TabManViewController.swift的viewDidLoad方法中
let firstVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FirstTabVC") as! FirstTabViewController
let secondVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondTabVC") as! SecondTabViewController

// 直接绑定Delegate
firstVC.delegate = secondVC

viewControllers.append(firstVC)
viewControllers.append(secondVC)

// 删除以下代码
// let ad = UIApplication.shared.delegate as? AppDelegate
// ad?.firstVCInstance = firstVC
// ad?.secondVCInstance = secondVC

3. 修复CollectionView的索引和视图问题

// SecondTabViewController的cellForItemAt方法
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? UICollectionViewCell
    else {
        return UICollectionViewCell()
    }
    // 修正索引,去掉-1
    let imageData = collectedDogImages[indexPath.row]
    let appendingImageView = UIImageView(image: imageData)
    // 设置ImageView的frame和显示模式,确保能看到图片
    appendingImageView.frame = cell.contentView.bounds
    appendingImageView.contentMode = .scaleAspectFill
    appendingImageView.clipsToBounds = true
    cell.contentView.addSubview(appendingImageView)
    return cell
}

4. 修复InsertItems的索引错误

// SecondTabViewController的sendImage方法
func sendImage(with image: UIImage) {
    print("=")
    print("secondTab delegate method activated")
    
    self.collectedDogImages.append(image)
    // 数组索引从0开始,新增元素的位置是count-1
    let itemLocation = self.collectedDogImages.count - 1
    let indexPath = IndexPath(item: itemLocation, section: 0)
    
    self.collectionView.insertItems(at: [indexPath])
}

额外优化

补充getADog()方法的实现(确保图片下载后正确赋值):

// FirstTabViewController中添加
func getADog() {
    let headers: HTTPHeaders = ["x-api-key": apiKey]
    AF.request("https://api.thedogapi.com/v1/images/search", headers: headers).responseJSON { response in
        switch response.result {
        case .success(let value):
            if let jsonArray = value as? [[String: Any]], 
               let imageUrlStr = jsonArray.first?["url"] as? String, 
               let imageUrl = URL(string: imageUrlStr) {
                DispatchQueue.global().async {
                    if let imageData = try? Data(contentsOf: imageUrl), 
                       let image = UIImage(data: imageData) {
                        DispatchQueue.main.async {
                            self.dogImage = image
                            self.imageView.image = image
                        }
                    }
                }
            }
        case .failure(let error):
            print(error)
        }
    }
}

内容的提问来源于stack exchange,提问作者apple.cat.4683797

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 06:17:00