Завантажте подання із зовнішнього Xib-файлу в раскадровку


131

Я хочу використовувати представлення даних у кількох контролерах перегляду в дошці розповідей. Таким чином, я думав над тим, як створити подання у зовнішньому xib, так що зміни відображаються у кожному контролері перегляду. Але як можна завантажувати вигляд із зовнішнього xib на табло та чи це можливо? Якщо це не так, то які інші альтернативи можна використати, щоб відповідати ситуації, що склалася?



1
Дивіться це відео: youtube.com/watch?v=o3MawJVxTgk
malhobayyeb

Відповіді:


132

Мій повний приклад тут , але я наведу короткий опис нижче.

Макет

Додайте до свого проекту файл .swift та .xib з тим самим іменем. Файл .xib містить ваш власний макет подання (переважно, використовуючи обмеження автоматичної компонування).

Зробіть швидкий файл власником файлу xib.

введіть тут опис зображення Код

Додайте наступний код у файл .swift та підключіть розетки та дії з файлу .xib.

import UIKit
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView: UIView?

    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

Використай це

Використовуйте власний перегляд в будь-якому місці вашої розкадровки. Просто додайте UIViewі встановіть назву класу для вашого власного імені класу.

введіть тут опис зображення


3
Хіба це не loadNibNamed викликає init (кодер :)? У мене крах намагається адаптувати ваш підхід.
Рибалка

@Fishman, якщо ви спробуєте завантажити програму перегляду програмно (а не з розгортки), вона вийде з ладу, тому що наразі не має init(frame:). Докладніше див. У цьому підручнику .
Сурагч

7
Ще одна поширена причина збоїв - це не встановлення власного перегляду власнику файлу . Дивіться червоний круг у моїй відповіді.
Сурагч

5
Так, я встановив клас кореневого виду замість власника файлу, і це викликало нескінченний цикл.
devios1

2
Після додавання view.autoresizingMask = [.f prilagodWidth, .ffleHeight] рядок перед self.addSubview (view) він працює ідеально.
Прамод Тапанія

69

Якийсь час підхід Крістофера Свасі був найкращим підходом, який я знайшов. Я запитав про це пару старших дев у своїй команді, і один з них мав ідеальне рішення ! Це задовольняє кожне із занепокоєнь, на які так красномовно звертався Крістофер Свайсі, і він не вимагає коду підкласу котлованної панелі (моє головне питання щодо його підходу). Є одна готча , але крім цього вона досить інтуїтивна і проста у виконанні.

  1. Створіть спеціальний клас UIView у файлі .swift, щоб керувати своїм xib. тобтоMyCustomClass.swift
  2. Створіть .xib-файл і накладіть його як потрібно. тобтоMyCustomClass.xib
  3. Установіть File's Owner.xib файл як ваш власний клас ( MyCustomClass)
  4. GOTCHA: залиште classзначення (під identity Inspector) для власного перегляду у файлі .xib порожнім. Отже, у вашому користувальницькому перегляді не буде вказаного класу, але він матиме вказаного власника файлу.
  5. Підключіть свої торгові точки, як зазвичай ви користуєтесь Assistant Editor.
    • ПРИМІТКА. Якщо ви подивитесь на Connections Inspectorвас, ви помітите, що у Ваших торгових точках не посилаються на ваш спеціальний клас (тобто MyCustomClass), а швидше на посилання File's Owner. Оскільки File's Ownerвказано, що це ваш індивідуальний клас, торгові точки будуть підключатись та працюватимуть належним чином.
  6. Переконайтеся, що ваш власний клас має @IBDesignable перед оператором класу.
  7. Зробіть свій власний клас відповідним NibLoadableпротоколу, на який посилається нижче.
    • ПРИМІТКА. Якщо .swiftназва вашого .xibфайлу користувацького класу відрізняється від вашого імені файлу, то встановіть nibNameвластивість, щоб воно було іменем вашого .xibфайлу.
  8. Реалізуйте required init?(coder aDecoder: NSCoder)та override init(frame: CGRect)зателефонуйте, setupFromNib()як на приклад нижче.
  9. Додайте UIView до потрібної дошки та встановіть клас як власне ім’я класу (тобто MyCustomClass).
  10. Дивіться IBDesignable у дії, коли він малює ваш .xib у рекламній дошці з усією пристрастю та дивом.

Ось протокол, на який ви хочете посилатися:

public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}

І ось приклад того, MyCustomClassщо реалізується протокол (з ім'ям файлу .xib MyCustomClass.xib):

@IBDesignable
class MyCustomClass: UIView, NibLoadable {

    @IBOutlet weak var myLabel: UILabel!

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupFromNib()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupFromNib()
    }

}

ПРИМІТКА. Якщо ви пропустите Gotcha і встановите classзначення у вашому файлі .xib як ваш власний клас, воно не буде малюватись на аркуші розкадрівки, і ви отримаєте EXC_BAD_ACCESSпомилку під час запуску програми, оскільки вона застрягла у нескінченному циклі намагаючись ініціалізувати клас з нибу, використовуючи init?(coder aDecoder: NSCoder)метод, який потім викликає Self.nib.instantiateі викликає initзнову.


2
Ось ще один чудовий підхід до цього, але я вважаю, що вище все ще краще: medium.com/zenchef-tech-and-product/…
Бен Патч

4
Згаданий вами підхід відмінно працює і застосовує попередній перегляд прямо у дошці розповідей. Це абсолютно зручно і дивовижно, великий!
Ігор Леонович

1
FYI: це рішення, використовуючи визначення обмежень у setupFromNib(), здається, виправляє певні дивні проблеми з автоматичним компонуванням із клітинками перегляду таблиці з автоматичним розміром, що містять перегляди, створені XIB.
Гері

1
На сьогодні найкраще рішення! Я люблю @IBDesignableсумісність. Не можу повірити, чому Xcode або UIKit не надають щось подібне за замовчуванням при додаванні файлу UIView.
fl034

1
Здається, це не може зробити цю роботу .. Кожен раз, коли я встановлюю власника файлу всередині свого .xib-файлу, він також встановлює спеціальний клас.
drewster

32

Якщо припустити, що ви створили xib, який ви хочете використовувати:

1) Створіть спеціальний підклас UIView (ви можете перейти до Файл -> Нове -> Файл ... -> Какао сенсорний клас. Переконайтесь, що "Підклас:" є "UIView").

2) Додайте подання, яке базується на xib, як підпогляд до цього подання при ініціалізації.

В Obj-C

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
                                                              owner:self
                                                            options:nil] objectAtIndex:0];
        xibView.frame = self.bounds;
        xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview: xibView];
    }
    return self;
}

У Swift 2

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.addSubview(xibView)
}

У Swift 3

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.addSubview(xibView)
}

3) Де б ви не хотіли використовувати його у вашій дошці, додайте UIView так, як зазвичай, виберіть недавно доданий вигляд, перейдіть до інспектора ідентичності (третій значок у верхньому правому куті, який виглядає як прямокутник з рядками в ньому), і введіть ім'я свого підкласу як "Клас" у розділі "Спеціальний клас".


xibView.frame = self.frame;повинно бути xibView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);, інакше xibView матиме зміщення, коли подання буде додано до розкадрівки.
BabyPanda

пізно до партії, але, схоже, він змінив її на xibView.frame = self.bounds, що є кадром без зміщення
Heavy_Bullets

20
Результат аварії через нескінченну рекурсію. Завантаження nib створює ще один екземпляр підкласу.
Девід

2
Клас перегляду xib не повинен бути таким самим, як цей новий підклас. Якщо xib є MyClass, ви можете зробити цей новий клас MyClassContainer.
користувач1021430

розчин вище буде руйнуватися до нескінченної рекурсії.
JBarros35

27

Я завжди вважав рішення "додати його як підзапис" незадовільним, бачачи, що він вкручується з розетками (1), (2) @IBInspectableта (3). Замість цього дозвольте мені познайомити вас з магією awakeAfter:, в NSObjectметоді.

awakeAfterдозволяє обміняти об'єкт, фактично прокинутий з NIB / Дошки розгортки, повністю іншим об'єктом. Потім цей об'єкт ставиться через процес гідратації, awakeFromNibзакликає його, додається як перегляд тощо.

Ми можемо використати це в підкласі "вирізання з картону" нашого перегляду, єдиною метою якого буде завантаження подання від НБУ та повернення його для використання на "Радці". Потім вкладений підклас задається інспектором особистих даних перегляду «Розгортка», а не початковим класом. Насправді це не повинно бути підкласом, щоб це працювало, але зробити його підкласом - це те, що дозволяє IB бачити будь-які властивості IBInspectable / IBOutlet.

Цей додатковий котлован може здатися неоптимальним - і в певному сенсі це є, тому що в ідеалі він UIStoryboardби справлявся з цим безпроблемно - але він має перевагу в тому, що вихідний NIB і UIViewпідклас повністю не змінюються. Роль, яку вона відіграє, в основному грає клас адаптера або моста, і цілком справедлива, дизайнерська, як додатковий клас, навіть якщо про це шкодує. Якщо ви віддаєте перевагу, якщо ви віддаєте перевагу своїм класам, рішення @ BenPatch працює, впроваджуючи протокол із деякими іншими незначними змінами. Питання про те, яке рішення краще, зводиться до питання стилю програміста: чи віддати перевагу композиції об'єкта чи багатократному успадкуванню.

Примітка: клас, встановлений у поданні у файлі NIB, залишається колишнім. Вкладений підклас використовується лише у розкадровці. Підклас не можна використовувати для примірника подання в коді, тому він сам не повинен мати додаткової логіки. Він повинен тільки утримувати awakeAfterгак.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
  }
}

One Важливим недоліком тут є те, що якщо ви визначите обмеження у ширині, висоті чи співвідношенні сторін на дошці даних, які не стосуються іншого виду, їх потрібно копіювати вручну. Обмеження, що стосуються двох поглядів, встановлюються на найближчого загального предка, і погляди пробуджуються із дошки розгортання зсередини, тому до того моменту, коли ці обмеження будуть гідратовані на огляді, заміни вже відбулися. Обмеження, які стосуються лише розглянутого перегляду, встановлюються безпосередньо на цьому огляді, і, таким чином, отримують переміщення, коли відбувається заміна, якщо вони не скопійовані.

Зауважте, що тут відбувається обмеження, встановлене на поданні на дошці розкадрування , скопійовано в нещодавно створений вигляд , який, можливо, вже має власні обмеження, визначені у його файлі ніб. Ці не зачіпаються.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!

    for constraint in constraints {
      if constraint.secondItem != nil {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
      } else {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
      }
    }

    return newView as Any
  }
}  

instantiateViewFromNibє безпечним розширенням до UIView. Все, що він робить, - провести цикл через об'єкти NIB, поки не знайде той, який відповідає типу. Зауважте, що загальний тип - це значення, що повертається , тому тип потрібно вказати на сайті виклику.

extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}

Це розумний спорт. Якщо я не помиляюся, це працює лише "на дошці розкадрівки" - якщо ви спробуєте створити такий клас у коді під час виконання, я не думаю, що це працює. Я вірю.
Fattie

Підклас повинен працювати в коді так само, як і початковий клас для всіх намірів і цілей. Якщо ви хочете завантажити подання з нибу в код, ви просто заповнити його безпосередньо за допомогою тієї ж методики. Все, що підклас - це взяти код, щоб миттєво подати подання від кулачка, і покласти його в гачок, щоб використовувати його.
Крістофер Свайсі

Насправді я помилявся - він би спрацював так само добре, якби ви могли його створити, але ви не можете, оскільки представлення в NIB матиме суперклас як його тип, тому instantiateViewFromNibнічого не поверне. Це не велика справа в будь-якому випадку ІМО, підклас - це лише привід підключитися до аркуша розкадровки, весь код повинен бути на оригінальному класі.
Крістофер Свайсі

1
Чудово! Одна річ спонукала мене, тому що у мене мало досвіду роботи з xibs (я коли-небудь працював із дошками та програмними підходами), залишаючи це тут, якщо це комусь допомагає: у файлі .xib потрібно вибрати подання верхнього рівня та встановити тип класу до MyCustomView. У моєму xib за замовчуванням ліва внутрішня бічна панель відсутня; щоб увімкнути його, поруч із нижньою та лівою стороною є кнопка поруч із функціями "Погляд як: iPhone 7".
xaphod

5
Він гальмує обмеження, коли його замінює інший об’єкт. :(
invoodoo

6

Найкращим рішенням на даний момент є просто використовувати користувальницький контролер подання з його представленням, визначеним у xib, та просто видалити властивість "view", яку Xcode створює всередині рекламної панелі, додаючи до неї контролер перегляду (не забудьте встановити ім'я користувацький клас, хоча).

Це змусить час виконання автоматично шукати xib і завантажувати його. Ви можете використовувати цей трюк для будь-якого виду контейнерів або перегляду вмісту.


5

Я думаю про alternativeвикористання XIB viewsдля використання View Controller в окремій раскадровці .

Потім в головному розкадровки замість використання призначеного для користувача зору container viewз Embed Segueі мати StoryboardReferenceдо цього призначеному для користувача увазі контролера , який вигляд повинен бути розміщений всередині іншого виду в головному вікні редактора.

Тоді ми можемо налаштувати делегування та комунікацію між цим вбудованим ViewController та головним контролером перегляду через підготовку до segue . Цей підхід відрізняється від відображення UIView, але набагато простіше та ефективніше (з точки зору програмування) можна використовувати для досягнення тієї ж мети, тобто мати користувацький перегляд для багаторазового використання, який видно на головній дошці розгортки

Додатковою перевагою є те, що ви можете реалізувати вашу логіку в класі CustomViewController і налаштовувати всю підготовку до делегування та перегляду без створення окремих (важче знайти в проекті) класів контролерів та без розміщення кодового коду в головному UIViewController за допомогою Component. Я думаю, що це добре для багаторазових компонентів. Компонент музичного плеєра (подібний віджет), який можна вбудувати в інші представлення даних.


На жаль, набагато простіше рішення, ніж використання користувальницького перегляду xib :(
Wilson

1
після проходження по колах із ланцюжком створення життєвого циклу яблука на користувальницькому UIView - це я так і закінчив.
LightningStryk

У випадку, якщо ви хочете динамічно додати спеціальний вид, нам все одно доведеться перейти з окремим контролером перегляду (найкраще XIB)
Satyam

5

Хоча найкращі відповіді на найпопулярніші відповіді добре працюють, вони концептуально помиляються. Всі вони використовуються File's ownerяк з'єднання між розетками класу та компонентами інтерфейсу. File's ownerпередбачається використовувати лише для об'єктів верхнього рівня, а не UIViews. Перегляньте документ розробника Apple . Наявність UIView як File's ownerпризводить до цих небажаних наслідків.

  1. Ви змушені використовувати contentViewтам, де вам належить користуватися self. Це не тільки некрасиво, але й структурно неправильно, оскільки проміжний вигляд утримує структуру даних від передачі його структури UI. Це як протилежне деклараційному інтерфейсу користувача.
  2. Ви можете мати лише один UIView на Xib. Передбачається, що Xib має кілька UIView.

Є елегантний спосіб зробити це без використання File's owner. Перевірте цю публікацію в блозі . Це пояснює, як це зробити правильно.


це зовсім неважливо, ти не пояснив, чому це погано. Я не можу зрозуміти, чому. побічних ефектів немає.
JBarros35

1

Ось відповідь, яку ви хотіли весь час. Ви можете просто створити свій CustomViewклас, мати його головний екземпляр у xib з усіма підвідними та вихідними точками. Тоді ви можете застосувати цей клас до будь-яких примірників у ваших дошках розмов чи інших xibs.

Не потрібно сваритися з власником файлу, або підключати розетки до проксі-сервісу, або модифікувати xib своєрідним чином, або додавати екземпляр власного перегляду у вигляді самого підзагляду.

Просто зробіть це:

  1. Імпортуйте рамки BFWControls
  2. Зміна суперкласу від UIViewдо NibView(або UITableViewCellв NibTableViewCell)

Це воно!

Він навіть працює з IBDesignable, щоб посилатися на ваш власний перегляд (включаючи підпогляди від xib) під час проектування в розгортці.

Більше про це можна прочитати тут: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

А ви можете отримати рамку BFWControls з відкритим кодом тут: https://github.com/BareFeetWare/BFWControls

Ось простий витяг з NibReplaceableкоду, який керує ним, якщо вам цікаво:
 https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

Том 👣


Вишукане рішення, дякую!
проспект

Я не хочу встановлювати рамку, коли вона повинна надаватися в оригіналі.
JBarros35

0

Це рішення можна використовувати, навіть якщо ваш клас не має того самого імені, як XIB. Наприклад, якщо у вас є контролер базового виду контролераA, у якого є ім'я XIB controllerA.xib, і ви підкласифікували це до controllerB і хочете створити екземпляр controllerB на дошці розкадрування, ви можете:

  • створити контролер перегляду в дошці
  • встановити клас контролера на контролерB
  • видаліть подання контролераB на дошці розкадрування
  • перегляд перегляду навантаження в контролеріA на:

*

- (void) loadView    
{
        //according to the documentation, if a nibName was passed in initWithNibName or
        //this controller was created from a storyboard (and the controller has a view), then nibname will be set
        //else it will be nil
        if (self.nibName)
        {
            //a nib was specified, respect that
            [super loadView];
        }
        else
        {
            //if no nib name, first try a nib which would have the same name as the class
            //if that fails, force to load from the base class nib
            //this is convenient for including a subclass of this controller
            //in a storyboard
            NSString *className = NSStringFromClass([self class]);
            NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:@"nib"];
            UINib *nib ;
            if (pathToNIB)
            {
                nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]];
            }
            else
            {
                //force to load from nib so that all subclass will have the correct xib
                //this is convenient for including a subclass
                //in a storyboard
                nib = [UINib nibWithNibName: @"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]];
            }

            self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
       }
}

0

Рішення для Objective-C відповідно до кроків, описаних у відповіді Бена Патча .

Використовуйте розширення для UIView:

@implementation UIView (NibLoadable)

- (UIView*)loadFromNib
{
    UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
    xibView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:xibView];
    [xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
    [xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
    [xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
    [xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
    return xibView;
}

@end

Створення файлів MyView.h, MyView.mі MyView.xib.

Спочатку підготуйте MyView.xibвідповідь БенаMyView Патча так, щоб встановити клас для власника File замість основного перегляду всередині цього XIB.

MyView.h:

#import <UIKit/UIKit.h>

IB_DESIGNABLE @interface MyView : UIView

@property (nonatomic, weak) IBOutlet UIView* someSubview;

@end

MyView.m:

#import "MyView.h"
#import "UIView+NibLoadable.h"

@implementation MyView

#pragma mark - Initializers

- (id)init
{
    self = [super init];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self loadFromNib];
    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self internalInit];
}

- (void)internalInit
{
    // Custom initialization.
}

@end

А пізніше просто створити свій погляд програмно:

MyView* view = [[MyView alloc] init];

Увага! Попередній перегляд цього виду не відображатиметься на сторінці розказок, якщо ви використовуєте розширення WatchKit через цю помилку в Xcode> = 9.2: https://forums.developer.apple.com/thread/95616


Повинен був забезпечити версію Swift. Наразі мало хто може використовувати ObjC досі
Satyam
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.