Задать вопрос
  • Что лучше для разработки игр под iOS: Unity или swift?

    dollar
    @dollar
    Делай добро и бросай его в воду.
    В общем случае лучше Unity.
    Потому что по современным меркам это шедевр. Сочетание простоты освоения (относительной, конечно) и большого числа возможностей.
    Плюсы
    • Низкий порог входа
    • Бесплатно
    • Комьюнити, учебники
    • Эффекты, графика
    • Куча интеграций (реклама) и готовых игровых систем (поиск пути и пр.)
    • Куча SDK ориентированы именно на Unity
    • Куча ассетов (почти бесплатно)
    • Кроссплатформенность

    А так зависит от игры. К примеру, если у вас игры довольно простые будут, и вы вообще не будете смотреть в сторону андроид, и очень сильный акцент хотите поставить на размере приложения и/или на быстродействии и энергопотреблении, т.е. будете холить и лелеять свою игру, вылизывать до идеала, то можно и Swift.

    P.S. Кстати, ставить вопрос как "Unity или Swift" - не совсем правильно, т.к. эти термины из разных категорий.
    Ответ написан
    4 комментария
  • Что лучше для разработки игр под iOS: Unity или swift?

    @Stas_Yaroslavtsev
    Начинающий игродел и веб-разработчик
    Ну это вам решать )
    Но на вашем месте я бы выбрал Unity
    Ответ написан
    5 комментариев
  • Как удалить кэш у пользователей приложения iOS при выпуске обновления?

    ivanvorobei
    @ivanvorobei
    iOS разработчик, канал https://t.me/sparrowcode
    Возможно вы изменили структуру БД и не сделали миграцию.

    Кэша, который мог бы навредить, нету в iOS разработке.
    Ответ написан
    3 комментария
  • Как седлать индикатор загрузки при прокрутки таблицы в верх?

    @davidnum95
    Это ж обычный pull to refresh https://stackoverflow.com/questions/24475792/how-t...
    Ответ написан
    Комментировать
  • Можно ли установить язык показываемого ViewController вне зависимости от языка устройства?

    ivanvorobei
    @ivanvorobei
    iOS разработчик, канал https://t.me/sparrowcode
    Код:

    func getFrenchString(forKey key: String) -> String {
        if let currentLanguage = (NSUserDefaults.standardUserDefaults().arrayForKey(AppleLanguages)?.first as? String) {
            if currentLanguage == "fr" {
                return NSLocalizedString(key, comment: "")
            }
            else {
                //the application is not currently on `fr`
                //change application to `fr`
                NSBundle.setLanguage("fr")
    
                //get the localized string on `fr`
                let frString = NSLocalizedString(key, comment: "")
    
                //return the application to the old language
                NSBundle.setLanguage(currentLanguage)
    
                return frString
            }
        }
    
        return ""
    }


    Как это работает
    Предпочитаемый язык хранится в NSUserDefaults, чтобы получить значение по ключу для конкретной локализации, временно меняем язык на Французский (вы можете поменять на любой другой), получаем значение и возвращаем оригинальный язык.

    Уверен, сможете сделать метод универсальным: к примеру, чтобы получал идентификатор локали как параметр в методе.
    Ответ написан
    Комментировать
  • Как получить файл из Realm?

    ivanvorobei
    @ivanvorobei Автор вопроса
    iOS разработчик, канал https://t.me/sparrowcode
    Делаем объект Data:

    let data = try Data(contentsOf: realmURL)

    realmURL в моем случае это fileURL из вопроса.
    Сохраняем с расширением:

    guard let shareFile = data.toFile(fileName: "debt-backup", extenshion: "debt") else { return }


    Метод toFile:

    func toFile(fileName: String, extenshion: String) -> URL? {
            
            if let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
                
                let fileURL = directory.appendingPathComponent(fileName + "." + extenshion)
                
                do {
                    try self.write(to: fileURL)
                    return fileURL
                }
                catch {
                    print("Error writing the file: \(error.localizedDescription)")
                }
            }
            
            return nil
        }
    Ответ написан
    Комментировать
  • Каким образом реализовать хранение данных в мобильном приложении ios с вопросами и ответами на них?

    ivanvorobei
    @ivanvorobei
    iOS разработчик, канал https://t.me/sparrowcode
    Конечно JSON, даже не сомневайся.
    Архитектура правильная, чуть шлифануть:

    {
       "id": "1", //  всё отлично
       "title": "Какой формы земля?", // можно question
       "type": "buttons", // Если это стиль отображения, тогда сделай массив. Если тип вопроса - всё ок
       "answers" {
          "correct": "Плоская"
          "incorrect": ["Круглая", "Квадратная", "Ромбовидная"]
       }
    }


    Для ответов обновил иерархию, неправильные ответы в массив. Правильные ответы хорошо бы тоже в массив.

    UPD2:
    А где правильнее хранить json файл?

    Если вопросы добавлять редко, то локально. Ради потенциальной возможности исправить ошибку мучать http не стоит)
    Ответ написан
    3 комментария
  • Как сделать бэкап Realm БД в файл на Swift?

    ivanvorobei
    @ivanvorobei Автор вопроса
    iOS разработчик, канал https://t.me/sparrowcode
    Если URL для Realm выглядит так:

    FileManager.default
                .containerURL(forSecurityApplicationGroupIdentifier: self.identificator)!
                .appendingPathComponent("default.realm")


    То получить объект Data не сложно:

    let backup = try Data(contentsOf: realmURL)

    Дальше Data можно записать куда-то, добавить ему расширение, выгрузить... тут по вкусу.

    Чтобы заменить базу данных Ream бэкапом, используйте:

    try backup.write(to: realmURL)

    Можно сохранить бэкап в файл, добавить расширение и предлагать пользователю сохранить файл (в iCloud Drive к примеру). Чтобы восстановиться из бэкапа, нужно открыть сохраненный файл.
    Ответ написан
    Комментировать
  • Как решить ошибку "unrecognized selector sent to instance"?

    doublench21
    @doublench21 Куратор тега Swift
    5d10efdae0912561988174.png

    Код
    func playVideo(filename: String) {
        let path = Bundle.main.path(forResource: filename, ofType:"mp4")!
        player = AVPlayer(url: NSURL(fileURLWithPath: path) as URL)
    
        playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = CGRect(x: 880, y: 270, width: 960, height: 540)
        scene!.view!.layer.addSublayer(playerLayer)
    
        player.actionAtItemEnd = .none
        player.play()
    
    
    
        DispatchQueue.main.async {
          NotificationCenter.default.addObserver(self,
                                                 selector: #selector(playerItemDidReachEnd),
                                                 name: NSNotification.Name.AVPlayerItemDidPlayToEndTime ,
                                                 object: self.player.currentItem)
        }
      }
    
      @objc func playerItemDidReachEnd(notification: NSNotification) {
        print("repeating...")
    
        let seconds : Int64 = 0
        let preferredTimeScale : Int32 = 1
        let seekTime : CMTime = CMTimeMake(value: seconds, timescale: preferredTimeScale)
    
        player.seek(to: seekTime)
        player.play()
      }
    Ответ написан
    1 комментарий
  • Как правильно сделать структуру?

    doublench21
    @doublench21 Куратор тега Swift
    Простой пример:

    2ffZng2.png

    Код
    let json = """
        [{
        "invoiceNumber": "FV/MON/X/369",
        "date": "2019-06-01 00:00:00",
        "dueDate": "2019-06-01 00:00:00",
        "overdue": true,
        "valueNoTax": 100,
        "valueWithTax": 123,
        "valueToPay": 123
        },
        {
        "invoiceNumber": "FV/MON/X/368",
        "date": "2019-06-01 00:00:00",
        "dueDate": "2019-06-01 00:00:00",
        "overdue": true,
        "valueNoTax": 100,
        "valueWithTax": 123,
        "valueToPay": 123
        },
        {
        "invoiceNumber": "FV/MON/X/367",
        "date": "2019-06-01 00:00:00",
        "dueDate": "2019-06-01 00:00:00",
        "overdue": true,
        "valueNoTax": 100,
        "valueWithTax": 123,
        "valueToPay": 123
        },
        {
        "invoiceNumber": "FV/MON/X/366",
        "date": "2019-06-01 00:00:00",
        "dueDate": "2019-06-01 00:00:00",
        "overdue": true,
        "valueNoTax": 100,
        "valueWithTax": 123,
        "valueToPay": 123
        },
        {
        "invoiceNumber": "FV/MON/X/363",
        "date": "2019-05-04 00:00:00",
        "dueDate": "2019-05-04 00:00:00",
        "overdue": true,
        "valueNoTax": 100,
        "valueWithTax": 123,
        "valueToPay": 123
        },
        {
        "invoiceNumber": "FV/MON/X/362",
        "date": "2019-05-04 00:00:00",
        "dueDate": "2019-05-04 00:00:00",
        "overdue": true,
        "valueNoTax": 100,
        "valueWithTax": 123,
        "valueToPay": 123
        },
        {
        "invoiceNumber": "FV/MON/X/361",
        "date": "2019-05-04 00:00:00",
        "dueDate": "2019-05-04 00:00:00",
        "overdue": true,
        "valueNoTax": 100,
        "valueWithTax": 123,
        "valueToPay": 123
        },
        {
        "invoiceNumber": "FV/MON/X/360",
        "date": "2019-05-04 00:00:00",
        "dueDate": "2019-05-04 00:00:00",
        "overdue": true,
        "valueNoTax": 100,
        "valueWithTax": 123,
        "valueToPay": 123
        }]
        """
    
        struct FakturyObject: Decodable {
          let date: String
          let dueDate: String
          let invoiceNumber: String
          let overdue: Bool // <---- ОШИБКА БЫЛА ТУТ! 
          let valueNoTax: Int
          let valueToPay: Int
          let valueWithTax: Int
        }
    
        let data = json.data(using: .utf8)
        let decoder = JSONDecoder()
        let parsedData = try! decoder.decode([FakturyObject].self, from: data!)
    Ответ написан
    1 комментарий
  • Как исправить ошибку Declaration ‘pressesEnded(presses:withEvent:)’ has different argument labels from any potential overrides?

    doublench21
    @doublench21 Куратор тега Swift
    Ну Вам ведь даже сам Xcode пишет:
    Слушай, я вижу что такой метод есть, но почему-то у тебя отличаются названия агрументов. Может глянешь?


    Для Вас даже в Xcode есть встроенная документация - ⌘ + Shift + 0. Если ввести название этого метода и глянуть на аргументы, то становится ясно, что presses нужно заменить _ presses, а withEvent нужно заменить на with Тоже самое сделать и со вторым методом.

    func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?)


    P.S. Если Вы позиционируете себя iOS разработчиком, то как вы дальше та будете, если такая простая ошибка вызывает диссонанс.
    Ответ написан
    1 комментарий
  • Как сделать чтобы ScrollView скроллился до конца?

    ivanvorobei
    @ivanvorobei
    iOS разработчик, канал https://t.me/sparrowcode
    После открытия времени работы, нужно пересчитать contentSize у UIScrollView. Код, который вы привели, просто скрывает вьюхи.
    Ответ написан
    Комментировать
  • Swift/Java vs React Native. В чем разница и куда лучше смотреть?

    RomReed
    @RomReed
    JavaScript, Flutter, ReactNative, Redux, Firebase
    Добрый день. У меня есть опыт разработки на реакт натив 2 года. Его самый большой плюс это скорость разработки(вам не нужно искать 2 разрабов для android и ios). Вы пишите код сразу на две платформы таких образом выкатываете приложение на рынок в очень короткие сроки. Но у него есть и минусы - его производительность будет уступать нативному приложению написанному на Swift/Java. В практике было пару раз что заказчик быстро выкатывает приложение на рынок. Потом его не устраивает производительность и приложение переписывается под Swift/Java.
    Ответ написан
    1 комментарий
  • На счет StoryBoard и чистого кода?

    yakovmanshin
    @yakovmanshin
    Software Engineer
    1. Файлы .storyboard — это по сути XML-документы, в которых хранится структура интерфейса приложения. Если вы откроете сториборд в текстовом редакторе, то увидите что-то подобное:
    5cdf186b054f5546973533.png
    Код всех элементов хранится там. Но в реальной работе открывать исходный код сторибордов не требуется; элементы интерфейса подключаются к коду иначе — с помощью аутлетов и экшенов. Здесь вы найдете детальный гайд.

    2. Если вы только входите в разработку для iOS, изучите Intro to App Development with Swift и App Development with Swift — бесплатные учебники от Apple, которые очень доходчиво объясняют основные понятия и принципы, которые используются в разработке iOS-приложений.
    Познакомившись с базовыми концептами, переходите к более сложным вещам. На RayWenderlich.com можно найти много обучающих материалов по конкретным технологиям и фреймворкам (например, SpriteKit или Core Data), а также пошаговые инструкции о том, как реализовать определенные решения в коде (например, раскрывающийся экран now playing в Apple Music). За видеокурсы нужно платить, но текстовые туториалы доступны бесплатно.
    Моя претензия к RayWenderlich — там недостаточно теории и технических деталей, которые необходимо изучать, чтобы не только повторять чужие решения задач, но и создавать свои. Хорошие материалы по теории есть на Swift by Sundell, freeCodeCamp, ну и на Medium, конечно. Что касается базовых конструкций языка, много информации есть на Swift.org.
    Ответ написан
    Комментировать
  • Синхронное выполнение кода SWIFT alamofire?

    doublench21
    @doublench21 Куратор тега Swift
    Добро пожаловать в мир DispatchQueue или OperationQueue на ваш выбор. Первый легче. Так как сама задача у вас является асинхронной, нужно будет немного поднапрячься. Для DispatchQueue нужно будет использовать барьеры, так как обычная serial очередь не подойдёт.

    Кидайте свои наработки, будем обсуждать дальше.
    Ответ написан
    Комментировать
  • Почему view controller неактивен после перехода на него с другого view controller через navigation controller, используя custom animation transition?

    doublench21
    @doublench21 Куратор тега Swift
    Быть может в completion вашей анимации надо все же сказать системе, что анимация завершена.

    transitionContext.completeTransition(true)
    Ответ написан
    1 комментарий
  • Как решить проблему с авто размером collection view?

    doublench21
    @doublench21 Куратор тега Swift
    Потому-что, так никто не делает, ну кроме Вас. Для работы auto layout нужно построить цепочку ограничений сверху вниз.

    Что бы не спрашивать что у Вас есть, а что нет, сразу напишу полный список./

    1) Прописать у layout коллекции:
    // ...
    layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    layout.itemSize = UICollectionViewFlowLayout.automaticSize


    2) Соврал, нужно уточнение.

    pronunciationLabel - Сколько строк должен занимать? (Только одну?) Увеличивается только в ширину?
    wordLabel - Тоже, что и pronunciationLabel? Или увеличивается в высоту?

    3) Этот бред больше никогда не нужно делать.

    contentView.translatesAutoresizingMaskIntoConstraints = false
       
            NSLayoutConstraint.activate([
                contentView.leftAnchor.constraint(equalTo: leftAnchor),
                contentView.rightAnchor.constraint(equalTo: rightAnchor),
                contentView.topAnchor.constraint(equalTo: topAnchor),
                contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
            ])


    Уточнение получены. Продолжим.

    Для начала уточним, как нам добиться динамического размера вьюхи в ширину и высоту. Приведу простой и наглядный пример. Создам вьюхи и помещу туда два лейбла. Пишу всё исключительно кодом, т.к. не использую сториборд, но думаю свести проблем не составит. И так:

    5cab4bce2a9a6308684642.png
    Код текстом
    let v = UIView(frame: .zero)
    
        let l1 = UILabel(frame: .zero)
            l1.translatesAutoresizingMaskIntoConstraints = false
            l1.font = .systemFont(ofSize: 27.0)
            l1.text = "asdlas"
    
        v.addSubview(l1)
        NSLayoutConstraint.activate([
          l1.topAnchor.constraint(equalTo: v.topAnchor),
          l1.leadingAnchor.constraint(equalTo: v.leadingAnchor),
        ])
        l1.setContentHuggingPriority(.required, for: .vertical)
        l1.setContentCompressionResistancePriority(.required, for: .vertical)
    
        let trailing = l1.trailingAnchor.constraint(equalTo: v.trailingAnchor)
            trailing.priority = .defaultHigh
        NSLayoutConstraint.activate([
          trailing
        ])
    
        let l2 = UILabel(frame: .zero)
            l2.translatesAutoresizingMaskIntoConstraints = false
            l2.font = .systemFont(ofSize: 17.0)
            l2.text = ""
    
        v.addSubview(l2)
        NSLayoutConstraint.activate([
          l2.topAnchor.constraint(equalTo: l1.bottomAnchor),
          l2.leadingAnchor.constraint(equalTo: v.leadingAnchor),
        ])
        l2.setContentHuggingPriority(.required, for: .vertical)
        l2.setContentCompressionResistancePriority(.required, for: .vertical)
    
        let trailing_ = l2.trailingAnchor.constraint(equalTo: v.trailingAnchor)
            trailing_.priority = .defaultHigh
        let bottom = l2.bottomAnchor.constraint(equalTo: v.bottomAnchor)
            bottom.priority = .defaultHigh
        NSLayoutConstraint.activate([
          trailing_,
          bottom
        ])
    
    
        let s = v.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
    
        view.addSubview(v)
        v.frame.size = s
        v.center = view.center
        v.layer.borderColor = UIColor.red.cgColor
        v.layer.borderWidth = 1.0


    В принципе мы делаем обычные констрениты, задавая топ и лидинг. Примечателен тут другой момент, а именно трейлинг. Перед его заданием, мы обязательно установили св-ва сопротивление сжатию и сопротивления расстяжения обоим лейблам, давая тем самым системе понять, что хотим видеть лейбл ровно такой ширины, какой он есть на самом деле. После мы делаем констрейнт трейлинг и указываем ему приоритет немного ниже чем 1000(required). Кстати, быть может в сториборде приоритет снижать не придется. Проверьте. (Кажись система делаем это за вас)

    С шириной всё готово. Смотрим на костреинты для высоты. Тут вполне обычно, начинаем с топ. После нижний лейбл крепится к боттом первого. И снова небольшая магия. Последний нижний констрейнт снова с пониженным приоритетом. Кстати, быть может в сториборде приоритет снижать не придется. Проверьте. (Кажись система делаем это за вас)

    Если быть точнее снижая приоритет(по крайне мере если задавать их кодом) такой подход даёт понять системе, что высоту и ширину он прямиком и полностью должен расчитывать исходя из высоты и ширины его сабвьюх, а не назначить свои значения.

    Поглядим на примеры:
    Первая имеет меньший размер шрифта, но больше символов.
    HC2w483.png
    Первая имеет больший размер шрифта, но больше символов.
    5MhP2nj.png
    Первая имеет меньший размер шрифта, но меньше символов.
    V00A7Qc.png
    Первая имеет больший размер шрифта, но меньше символов.
    zXH8glH.png

    Как видимо размер вьюхи система посчитала вольностью верным. То есть мы динамически научились задавать высоту и ширину ячейки.

    Поехали дальше.

    Но для ячеек есть ещё одна небольшая проблема. Система лайаута коллекции имеет все нужные констреинты, но она ничего не знает о данных в этих ячеек. А ведь именно они задают ширину ячеейки. Для этого нам нужно в какой-то момент времени немного подсказать системе.

    Воспользуемся методом:
    5cab50033a6e2506454233.png
    Код текстом
    func preferredLayoutAttributesFitting(
        _ layoutAttributes: UICollectionViewLayoutAttributes
      ) -> UICollectionViewLayoutAttributes
      {
        let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
    
        let fittingSize = UIView.layoutFittingCompressedSize
    
        layoutAttributes.frame.size = systemLayoutSizeFitting(
          fittingSize,
          withHorizontalFittingPriority: .fittingSizeLevel,
          verticalFittingPriority: .fittingSizeLevel
        )
    
        return layoutAttributes
      }


    Так как все констреинты у нас настроены, нам осталась попросить систему подсчитать нужные размеры и передать новые размеры системе лайаута коллекции. В этот момент кстати, все данные ячейка уже получила. (Текст для лейблов)

    И малая ремарка, возможно в метод preferredLayoutAttributesFitting после вызова super. надо будет добавить вызов метода layoutIfNeeded(). А может и не нужно.

    Итого:
    Добавляем то, что в пункте 1.
    Устанавливаем нужные констреинты.
    Удаляем ваш код в пункте 3.
    Добавляем метод preferredLayoutAttributesFitting
    Ответ написан
    3 комментария
  • Простой Hello World на Swift для вэб?

    Sanasol
    @Sanasol Куратор тега Веб-разработка
    нельзя просто так взять и загуглить ошибку
    html страница она и будет html страницей в любом случае. Фреймворк для этого не нужен.
    А что её отдать именно с помощью Swift надо поднять на нём сервер, и фреймворки здесь совсем не причём.
    Swift http server -> google
    https://github.com/httpswift/swifter

    Фреймворки типа Perfect, Vapor и др. -- не подойдут.

    https://github.com/Wolg/awesome-swift#http
    Ответ написан
    2 комментария
  • Как преобразовать структуру Firebase в объект swift?

    doublench21
    @doublench21 Куратор тега Swift
    Что из перечисленного списка Вас смущает и Вы не умеете это делать: ?
    • Получить JSON данные через URLSession/Alamofire/FirebaseSDK
    • Использование Codable для преобразования JSON в класс/структуру
    Ответ написан
    2 комментария
  • Как передать данные в кастомную ячейку UITableViewCell перед тем, как она будет построена?

    doublench21
    @doublench21 Куратор тега Swift
    Пример из моего проекта. (Код не привожу текстом, ужасный редактор.)

    В делегате:
    5c7bf8b3c0d26725364424.png
    internal override func collectionView(
      _ collectionView: UICollectionView,
        cellForItemAt indexPath: IndexPath
      ) -> UICollectionViewCell
      {
        let cell = collectionView.dequeueReusableCell(
          withReuseIdentifier: CategoriesCollectionViewCell.reuseIdentifier,
          for: indexPath
        ) as! CategoriesCollectionViewCell
    
        let randomIndex = indexPath.item % CategoriesPlaceholder.images.count
    
        cell.configure(
          withImage: UIImage(named: CategoriesPlaceholder.images[randomIndex])!,
          andTitle: CategoriesPlaceholder.titles[randomIndex]
        )
    
        return cell
      }

    Внутри ячейки:
    5c7bf8c1617ce913362365.png
    internal final class CategoriesCollectionViewCell: UICollectionViewCell {
    
     // =====================================
      // MARK: - Configure
      // =====================================
    
      internal func configure(withImage image: UIImage?, andTitle title: String) {
        illustrationCircle.image = image
        self.title.text = title
      }
    Ответ написан
    7 комментариев