• Как правильно работать с One Time Password (OTP)?

    @dmtrrr
    Backend developer
    В БД нужно хранить код, отправленный пользователю.
    Ответ написан
    5 комментариев
  • Как правильно реализовать подгрузку уведомлений?

    ivanvorobei
    @ivanvorobei
    iOS разработчик, канал https://t.me/sparrowcode
    Вы ищите способ реализации обратной связи. Концептуально вариантов не много

    1. Long Pooling - каждый раз шлете http запрос, при наличии ивента сервер перестает держать его и отвечает данными. Не видел проектов на этом, но на стороне бэкенда частенько.
    2. Пуши - запрашиваемое разрешение относится именно к отображению пушей. Даже без запаса разрешения вы будете их получать, пока приложение открыто. Хороший вариант для обратной связи.
    3. Вебсокеты - своего рода флагман, под узкие нужды. Прямая связь сервер-клиент, требуется установить стабильное соединение. Большинство месенджеров используют эту технологию.


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

    Учтите, что бэкенд, от которого вы хотите получать эти данные, должен поддерживать реализацию. Просто так к какому-угодно любым способом подключиться не получится.

    P.S. Вариант слать запрос раз в N секунд не предлагаю, потому что считаю это извращением.
    Ответ написан
    8 комментариев
  • На чем лучше строить проект?

    Сергей правильно расписал. Для ещё большей гибкости я бы посоветовал использовать очереди.
    Тогда первый сервис будет парсить tcp-соединения и класть сообщение в очередь (RabbitMQ, Kafka). Вторая часть, которая будет бэкендом для мобильных приложений, будет читать ещё и очередь. При получении сообщения из очереди, записывает в базу и шлёт PUSH сообщение на мобилки. Также мобилки могут иногда сами запросить данные (при первом запуске или при pull-to-refresh фиче, например), тогда ваше API вернёт данные из базы.

    Если нужен прям реальный realtime, то тогда это вопрос другого порядка. Скорее всего нужно написать демона, который будет парсить tcp и транслировать UDP. А мобилки будут на это подписываться. Но тут есть нюансы с авторизацией и т.п. Зависит от вашего функционала. Но чаще всего настоящий realtime не нужен. Задержка в секунду более чем допустима для тех сервисов, которые нас окружают.
    Ответ написан
    1 комментарий
  • На чем лучше строить проект?

    sergey-gornostaev
    @sergey-gornostaev Куратор тега Python
    Седой и строгий
    Стоит написать два сервиса: Один будет принимать tcp-соединения от железа и писать информацию в БД, а второй будет обрабатывать http-запросы от мобильных приложений. Только websocket'ы для общения с мобильными устройствами - это плохая идея. Во-первых, батарею будет жрать, а во-вторых, будут частые обрывы. Лучше использовать push-уведомления и краткосрочные соединения.
    Ответ написан
    1 комментарий
  • Как реализовать дизайн?

    ivanvorobei
    @ivanvorobei
    iOS разработчик, канал https://t.me/sparrowcode
    Вряд ли вверху оригинальный бар. Скорее всего повторили шрифты и добавили анимацию.
    Я бы сделал таблицу на весь экран с большим отступом вверху. В этот отступ поместил вьюхи, фейковый навигейшн не на скролл.
    Анимации привязали к прогрессу UScrollView.
    Ответ написан
    7 комментариев
  • Как лучше реализовать?

    sergey-gornostaev
    @sergey-gornostaev Куратор тега Python
    Седой и строгий
    Нужно просто сохранять все поисковые запросы в отдельную таблицу, а когда нужен топ3, делать из этой таблицы выборку с группировкой и агрегацией.
    Ответ написан
    4 комментария
  • Как правильно оценивать разработку есть ли методики и сервисы?

    @Stalinko Куратор тега Фриланс
    PHP'шник и фрилансер до мозга костей
    Оценка работы - это целая наука.
    Обычно проект разбивается на мелкие части, которые уже оцениваются в часах. В зависимости от количества часов устанавливается цена в деньгах.
    Оценка в часах - целое искусство, которому учатся всю жизнь. Отдельно расписывать не буду.

    В любом случае всё очень индивидуально. Как оценка по времени, так и по стоимости.
    Ответ написан
    Комментировать
  • Как лучше обновлять контент в приложении?

    doublench21
    @doublench21 Куратор тега Swift
    Дисклеймер: Не изобретайте велосипед!


    У меня глаза на лоб лезут от подобных вопросов.

    Ну есть у Вас данные, которые на момент запуска приложения должны быть строго актуальными, ТО это самые обычные запросы на сервер. Ровно так же как и в любом другом приложении. Заходишь в приложение, приложение отправляет запрос на сервер, а сервер отдаёт. Вы парсите, после отображаете информацию. НЕТ абсолютно никакой разницы изменилось там что-то или нет. Вы ведь просто запрашиваете JSON.

    Я ещё могу понять, если вы передаёте некий файл размеров в 10GB и который не хочется качать снова, если он не изменился. Тут я могу понять ваши опасения. Ну даже в таком случае, такие вопросы решаются на сервере путём нужных заголовков. Один из них E-TAG. Всё уже давно за вас придумали.

    Возвращаясь к вопросу автора. Если всё о чем мы говорим - это лишь JSON составленный из БД, то не мучайте сами знаете что. Просто сделайте запрос и всё. Не важно изменились ли там данные или нет. Обо всём остальном позаботится кэш, как на клиенте(URLSession), так и на сервере.

    ---------------------------------------------------------------------------------------------------

    Когда делать запросы на сервер?

    Ну смотря где эти данные отображаются.
    • данные относятся глобально ко всему приложения и могут использоваться в любом контроллере, тогда:
      func application(
              _ application: UIApplication,
              willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
          ) -> Bool {
              // ...
              return true
          }

      или
      func application(
              _ application: UIApplication,
              didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
          ) -> Bool {
             // ...
              return true
          }

      или как инициализация св-ва вообще в AppDelegate

    • если данные нужны лишь для конкретного контроллера, то как уже написали выше:
      override func viewDidLoad()  {
          super.viewDidLoad()
         // ...
       }

    Ответ написан
    8 комментариев
  • Как лучше обновлять контент в приложении?

    ivanvorobei
    @ivanvorobei
    iOS разработчик, канал https://t.me/sparrowcode
    Не понятно какую проблему вы пытаетесь решить.
    1. Нагрузка на сервер - вряд-ли статическим JSON вы хоть как то упретесь в половину мощности даже самой дешевой виртуалки на DO.
    2. Оперативка девайса - жрется от http запроса? Один контейнер реалма сожрет больше, а запрос по БД улетит в 100 порядков выше с их lazy запросами.
    3. performFetchWithCompletionHandler не для вашей задачи.

    Чтобы сформулировать проблему, нужно определить:
    • Насколько в актуальном состоянии должна быть информация (минута / месяц)
    • Насколько оперативно она должна доставляться (перед запуском, в основном потоке после запуска, в фоне после запуска, когда угодно),
    • На какие расходы вы готовы пойти (готов купить каплю на DO за 5$)

    Обновлю ответ после вашего комментария.
    Ответ написан
    4 комментария
  • Как сделать выезжающее из-под uitextfield оповещение?

    doublench21
    @doublench21 Куратор тега Swift
    Ну наверно как-то так, хотя за правильность я не ручаюсь.
    import UIKit
    
    class ViewController: UIViewController {
        @IBOutlet weak var textField: UITextField!
        private var substrateView = UIView()
        private var substrateLabel = UILabel()
        private var constraint = NSLayoutConstraint()
    
        @IBAction func someAction(_ sender: Any) {
            substrateView.isHidden = false
            UIView.animate(withDuration: 0.3) { [unowned self] in
                self.constraint.isActive = false
                self.constraint = self.substrateView.heightAnchor.constraint(equalToConstant: 60)
                self.constraint.isActive = true
                self.view.layoutIfNeeded()
            }
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            textField.layer.borderWidth = 3
            textField.layer.borderColor = UIColor(red: 233/255, green: 128/255, blue: 129/255, alpha: 1).cgColor
            textField.layer.cornerRadius = 8
    
            substrateView.backgroundColor = UIColor(red: 233/255, green: 128/255, blue: 129/255, alpha: 1)
            substrateView.layer.cornerRadius = 8
    
            substrateLabel.text = "Login is not valid"
            substrateLabel.textAlignment = .center
            substrateLabel.textColor = UIColor.white
            substrateLabel.backgroundColor = UIColor.clear
    
            substrateView.addSubview(substrateLabel)
    
            substrateLabel.translatesAutoresizingMaskIntoConstraints = false
            substrateLabel.frame = CGRect.zero
            substrateLabel.sizeToFit()
            substrateLabel.bottomAnchor.constraint(equalTo: substrateView.bottomAnchor).isActive = true
            substrateLabel.centerXAnchor.constraint(equalTo: substrateView.centerXAnchor).isActive = true
    
            view.addSubview(substrateView)
    
            substrateView.translatesAutoresizingMaskIntoConstraints = false
            substrateView.topAnchor.constraint(equalTo: textField.topAnchor).isActive = true
            substrateView.leftAnchor.constraint(equalTo: textField.leftAnchor).isActive = true
            substrateView.rightAnchor.constraint(equalTo: textField.rightAnchor).isActive = true
            substrateView.widthAnchor.constraint(equalTo:textField.widthAnchor).isActive = true
    
            constraint = substrateView.heightAnchor.constraint(equalTo: textField.heightAnchor)
            constraint.isActive = true
    
            textField.layer.zPosition = 1
            substrateView.isHidden = true
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
    
    }


    Вышло как-то так:
    giphy.gif
    Конечно сам action выберешь такой какой тебе нужен. На гифке - вызывается просто по нажатию.
    Ответ написан
    5 комментариев
  • Как построить иерархию sqlite и сам алгоритм реализации идеи?

    devspec
    @devspec
    Помогло? Отметь решением
    Забудьте о глобальных переменных.
    Сделайте две таблицы. Первая - дерево (tree).
    id   | name           | parent_id
    1    | Спорт          | null
    2    | Футбол         | 1
    3    | Чемпионат РФПЛ | 2

    Вторая - данные (data).
    id   |   tree_id  | data
    1    |   3        | Инфа о чемпионате РФПЛ

    Пользователю показывайте инфу из "дерева"и давайте выбрать категорию, когда выберет - показывайте данные, соответствующие категории:
    select * from data where tree_id=3
    Ответ написан
    Комментировать
  • Как сделать тень в uitextfield разную с двух сторон?

    doublench21
    @doublench21 Куратор тега Swift
    Достаточно просто. Нужно делать через свойство UIView.layer.shadowPath.
    import UIKit
    
    @IBDesignable
    class ViewController: UIViewController {
    
        @IBOutlet weak var textField: UITextField!
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            textField.layer.shadowColor = UIColor.purple.cgColor
            textField.layer.cornerRadius = 8
    
            textField.layer.masksToBounds = false
            textField.layer.shadowOffset = CGSize.zero
            textField.layer.shadowRadius = 3.0
            textField.layer.shadowOpacity = 0.7
    
            let path = UIBezierPath()
            path.move(to: CGPoint(x: 0.0, y: 0.0))
            path.addLine(to: CGPoint(x: textField.bounds.size.width/2, y: textField.bounds.size.height/2))
            path.addLine(to: CGPoint(x: textField.bounds.maxX, y: 0.0))
            path.addLine(to: CGPoint(x: textField.bounds.maxX, y: textField.bounds.maxY))
            path.addLine(to: CGPoint(x: 0.0, y: textField.bounds.maxY))
            path.close()
            textField.layer.shadowPath = path.cgPath
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    }

    Вышло так:
    beVqPQ2jSguwZ8-Vva6S9w.jpg
    Путь использовал такой:
    VIBE4c0dTAuFQXiDfhoLPA.jpg
    Тебе остаётся для лучшего качества сделать скажем такой путь:
    WmsQS6asSLenTPqh9GKKRQ.jpg
    Или более извилистый путь, если ты хочешь уменьшить глубину тени слева и справа.
    Ответ написан
    Комментировать