Я могу распечатать данные, но не могу назначить их метке в Swift

Я отправил свои данные из моего вызова API в мой InfoController viewDidLoad. Там я смог безопасно сохранить его в константе skillName, а также распечатать, получив всю информацию через консоль.

Проблема возникает, когда я пытаюсь присвоить эту переменную моему skillLabel.

override func viewDidLoad() {
    super.viewDidLoad()
    configureViewComponents()
    fetchPokemons { (names) in
        guard var skillName = names as? String else { return }
        self.pokemon?.skillName = skillName
        
        self.allNames = skillName
        print(self.allNames)
    }
}

Там, когда я печатаю allNames, консоль показывает все нужные мне данные. Вот как выглядят данные: Пример данных

И вычисляемое свойство, в котором я хочу использовать эти данные, выглядит так:

var pokemon: Pokemon? {
    didSet {
        guard let id        = pokemon?.id else { return }
        guard let data      = pokemon?.image else { return }

        
        navigationItem.title = pokemon?.name?.capitalized
        infoLabel.text = pokemon?.description
        infoView.pokemon = pokemon
        
        if id == pokemon?.id {
            imageView.image = UIImage(data: data)
            infoView.configureLabel(label: infoView.skillLabel, title: "Skills", details: "\(allNames)")
        }
    }
}

PD: allNames — это строковая переменная, которую я имею на уровне класса InfoController.

Вот как мое приложение выглядит при запуске: PokeApp

Моя цель - получить этот параметр деталей для отображения данных skillName, но он возвращает ноль, не знаю почему. Любой совет?

EDIT1: Моя функция, которая извлекает данные о покемонах из моего класса обслуживания, такова:

func fetchPokemons(handler: @escaping (String) -> Void) {
    controller.service.fetchPokes { (poke) in
        DispatchQueue.main.async {
            self.pokemon? = poke
            
            guard let skills = poke.abilities else { return }
            
            for skill in skills {
                
                guard let ability = skill.ability else { return }
                
                guard var names = ability.name!.capitalized as? String else { return }
                
                self.pokemon?.skillName = names
                handler(names)
            }
        }
    }
}

EDIT2: класс InfoView выглядит так:

class InfoView: UIView {

// MARK: - Properties
var delegate: InfoViewDelegate?

//  This whole block assigns the attributes that will be shown at the InfoView pop-up
//  It makes the positioning of every element possible
var pokemon: Pokemon? {
    didSet {
        guard let pokemon   = self.pokemon else { return }
        guard let type      = pokemon.type else { return }
        guard let defense   = pokemon.defense else { return }
        guard let attack    = pokemon.attack else { return }
        guard let id        = pokemon.id else { return }
        guard let height    = pokemon.height else { return }
        guard let weight    = pokemon.weight else { return }
        guard let data      = pokemon.image else { return }
        
        if id == pokemon.id {
            imageView.image = UIImage(data: data)
        }
        nameLabel.text = pokemon.name?.capitalized
        
        configureLabel(label: typeLabel, title: "Type", details: type)
        configureLabel(label: pokedexIdLabel, title: "Pokedex Id", details: "\(id)")
        configureLabel(label: heightLabel, title: "Height", details: "\(height)")
        configureLabel(label: defenseLabel, title: "Defense", details: "\(defense)")
        configureLabel(label: weightLabel, title: "Weight", details: "\(weight)")
        configureLabel(label: attackLabel, title: "Base Attack", details: "\(attack)")
    }
}

let skillLabel: UILabel = {
    let label = UILabel()
    return label
}()

let imageView: UIImageView = {
    let iv = UIImageView()
    iv.contentMode = .scaleAspectFill
    return iv
}()
. . .
}

infoView.configureLabel это:

func configureLabel(label: UILabel, title: String, details: String) {
    let attributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(title):  ", attributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: Colors.softRed!]))
    attributedText.append(NSAttributedString(string: "\(details)", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.gray]))
    label.attributedText = attributedText
}

РЕДАКТИРОВАНИЕ 3. Дизайн структур

struct Pokemon: Codable {
    var results: [Species]?
    var abilities: [Ability]?
    var id, attack, defense: Int?
    var name, type: String?
...
}

struct Ability: Codable {
    let ability: Species?
}

struct Species: Codable {
    let name: String?
    let url: String?
}

person 3rnestocs    schedule 11.11.2020    source источник


Ответы (1)


Перейдите к абзацу Edit2, чтобы получить окончательный ответ!

Первоначальный ответ:

Похоже, ваш пользовательский интерфейс не обновляется после того, как контроллер извлекает все данные.

Поскольку весь ваш код конфигурации пользовательского интерфейса находится внутри var pokemon / didSet, рекомендуется извлечь его в отдельный метод.

private func updateView(with pokemon: Pokemon?, details: String?) {
    guard let id = pokemon?.id, let data = pokemon?.image else { return }

    navigationItem.title = pokemon?.name?.capitalized
    infoLabel.text = pokemon?.description
    infoView.pokemon = pokemon

    if id == pokemon?.id {
        imageView.image = UIImage(data: data)
        infoView.configureLabel(label: infoView.skillLabel, title: "Skills", details: details ?? "")
    }
}

и теперь вы можете легко вызвать didSet

var pokemon: Pokemon? {
    didSet { updateView(with: pokemon, details: allNames) }
}

а также fetchPokemons завершение

override func viewDidLoad() {
    super.viewDidLoad()
    configureViewComponents()
    fetchPokemons { (names) in
        guard var skillName = names as? String else { return }
        self.pokemon?.skillName = skillName

        self.allNames = skillName
        print(self.allNames)
        DispatchQueue.main.async {
            self.updateView(with: self.pokemon, details: self.allNames)
        }
    }
}

Очень важно выполнять любые настройки пользовательского интерфейса в основной очереди.

Редактировать:

Функция извлечения может быть причиной проблем! вы вызываете обработчик несколько раз:

func fetchPokemons(handler: @escaping (String) -> Void) {
    controller.service.fetchPokes { (poke) in
        DispatchQueue.main.async {
            self.pokemon? = poke
            guard let skills = poke.abilities else { return }
            let names = skills.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ")
            handler(names)
        }
    }
}

Редактировать2:

После просмотра вашей кодовой базы есть несколько вещей, которые вам нужно изменить:

1. fetchPokemons реализация

обработчик controller.service.fetchPokes вызывается для каждого покемона, поэтому нам нужно проверить, является ли выбранный текущий (self.pokemon), а затем вызвать handler с правильно отформатированными навыками.

func fetchPokemons(handler: @escaping (String) -> Void) {
    controller.service.fetchPokes { (poke) in
        guard poke.id == self.pokemon?.id else { return }
        self.pokemon? = poke
        let names = poke.abilities?.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ")
        handler(names ?? "-")
    }
}

2. обновить viewDidLoad()

теперь просто передайте значение names метке.

override func viewDidLoad() {
    super.viewDidLoad()
    configureViewComponents()
    fetchPokemons { (names) in
        self.pokemon?.skillName = names
        self.infoView.configureLabel(label: self.infoView.skillLabel, title: "Skills", details: names)
    }
}

3. Рефакторинг var pokemon: Pokemon? обозревателя didSet

var pokemon: Pokemon? {
    didSet {
        guard let pokemon = pokemon, let data = pokemon.image else { return }
        navigationItem.title = pokemon.name?.capitalized
        infoLabel.text = pokemon.description!
        infoView.pokemon = pokemon
        imageView.image = UIImage(data: data)
    }
}
person Witek Bobrowski    schedule 11.11.2020
comment
а если передать self.pokemon?.skillName вместо self.allNames? - person Witek Bobrowski; 12.11.2020
comment
тот же результат, а также мой CollectionView загружается медленнее, чем до рефакторинга - person 3rnestocs; 12.11.2020
comment
Не могли бы вы включить еще немного кода в свой вопрос? Больше контекста было бы здорово. Нужно проверить, совпадает ли infoView.skillLabel с отображаемым в данный момент. Вы упомянули collectionView, возможно, попробуйте вызвать reloadData() после завершения выборки? - person Witek Bobrowski; 12.11.2020
comment
ну, мое приложение работает следующим образом: CollectionViewController, который показывает изображение и имя для каждого элемента. Если вы нажмете один элемент, он перейдет к моему InfoViewController, где я действительно хочу показать навыки. Я думаю, что infoView.skillLabel определенно то же самое, что отображается в данный момент, иначе он не показывал бы пустое значение вместо значения, которое я хочу - person 3rnestocs; 12.11.2020
comment
хорошо, так что сам infoView.skillLabel находится не в каком-то UICollectionViewCell, а скорее в статической метке, верно? Я хотел бы увидеть реализацию infoView.configureLabel и, возможно, план infoView - person Witek Bobrowski; 12.11.2020
comment
Вот вам! . . . в InfoView, потому что я объявляю все метки и прочее, которые отображаются в моем InfoController, и я не думаю, что стоит это показывать. - person 3rnestocs; 12.11.2020
comment
вы вызываете handler(names) несколько раз для каждого навыка в массиве. Таким образом, у allNames всегда будет максимум 1 имя навыка! - person Witek Bobrowski; 12.11.2020
comment
если вы хотите объединить все имена навыков, попробуйте это вместо цикла for: let names = skills.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ") - person Witek Bobrowski; 12.11.2020
comment
проверить мой обновленный ответ с новой реализацией выборки - person Witek Bobrowski; 12.11.2020
comment
Каждый раз, когда вы обновляете пользовательский интерфейс из асинхронного вызова, оборачивайте его в DispatchQueue.main.async. Возможно, вы пропустили этот момент из этого ответа. - person Son Nguyen; 12.11.2020
comment
Я думаю, что проблема сейчас в этой строке skills.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ") - person 3rnestocs; 12.11.2020
comment
Дело в том, что моя способность?.name не является массивом, это просто строка. Вот почему у меня был цикл for, потому что мне нужно показать соответствующую строку для всех покемонов моего CollectionView - person 3rnestocs; 12.11.2020
comment
но разве навыки не являются массивом строковых значений? как вы хотите их отображать? разделенных запятыми? если вы сейчас напечатаете значение names, что вы получите? - person Witek Bobrowski; 12.11.2020
comment
Я получаю строку для каждого покемона, который у меня есть, разделенный запятыми. Проверьте это: i.gyazo.com/d9fb8a482b14a022919a2ec1ea66d719.png - person 3rnestocs; 12.11.2020
comment
и какой тип poke.abilities? - person Witek Bobrowski; 12.11.2020
comment
[Ability], и у этого свойства есть свойство типа [Species], и это структура, которая содержит все имена и URL-адреса для моего кода выборки. Я отредактирую свой пост с ними. - person 3rnestocs; 12.11.2020
comment
Вы хотите отправить мне проект xcode по электронной почте? это было бы намного проще, чем пытаться понять это из этого небольшого примера кода. нажмите на мой профиль и получите мою электронную почту с моего сайта github. - person Witek Bobrowski; 12.11.2020
comment
@3rnestocs Я обновил свой ответ рабочим решением - person Witek Bobrowski; 12.11.2020