Я человек далекий от программирования, живу за колючкой и люблю бить баклуши. Но случилось непредвиденное, моему сербскому другу Джонни, приехавшему в гости, понадобился QR-код. Пришлось расчехлить клавиатуру.
Краткое содержание первой серии
Ко мне приехал мой сербский приятель Никола Павлович, вакцинированный2 Пфайзером. В нашей стране его всюду просили показать QR-код или гуляй мимо. Ситуация идиотская. Человек дважды привит Пфайзером, а его никуда не пускают. Я был в бешенстве.
-Джонни, сгенерю-ка я тебе QR-код, - подумал я и сгенерил.
Как действует QR-код
Чтобы понять сценарий верификации кода, пришлось отправиться в местную Плазу (привет Виктору Левашову) и предъявить охраннику свой QR-сертификат. Откуда у меня сертификат? А я вакцинирован Спутником V. И вот что я обнаружил
охранник включил телефон
охранник включил камеру на телефоне
навел камеру на мой QR-код
нажал на всплывающий поп-ап
Проходи!
На экране телефона охранника высветилась информация обо мне в стыдливо прикрытом виде

Sic! В QR-коде просто зашифрован линк-ссылка на сайт Госуслуг. В чьей-то голове сформулировалось техническое задание о разработке приложения под iOS. Голова оказалась моя. И заметьте, информация на экране приложения не является персональной. Очевидно, что аббревиатура ФИО и 3 последние цифры паспорта никак не идентифицируют человека. По этим данным даже пол гражданина (гражданки?) не определить.
Техническое задание
Надо сделать сайт-пародию (дюжина строк кода на PHP) который отображает информацию из строки url-запроса типа http://govuslugi.her/vam_qr.php?string=BBB_15031964_22345
Надо сделать iOS приложение (пару дюжин строк на swift), которое будет генерить QR-код и показывать его на экране iPhone.
Серверная часть она же Web
Вообще, публикация планировалась как учебник по SwiftUI+MVVM+Reactive, но для общего понимания задачи приведу прототип серверной части.
Я сделал веб-страничку на PHP, она будет отображать входящую информацию, записанную в параметре name нашего url-запроса. Типа www.fig.vam/vsem.php?name=jonny_depp
Я извиняюсь, последний раз я кодировал на пе-хе-пе в 2005 году, поэтому прошу прощения за олд-стайл. Слава Джобсу, в PHP не надо проверять строку на её существование и на наличие в ней символов в нужных местах, код никогда не ломается и это прекрасно. Ой, тухлые помидоры!
if (isset($_GET['name'])) $name=($_GET['name']); else $name="Xerr"; list($fio, $birth, $pass) = explode("_", $name); $user = m_substr($fio, 0, 1, 'UTF-8')."***** ".m_substr($fio, 1, 1, 'UTF-8')."***** ".mb_substr($fio, 2, 1, 'UTF-8')."********"; $passport = "Паспорт: ".substr($pass, 0, 2)."*****".substr($pass, 2, 3); $birthday = "Дата рождения: ".$birth;
Как видите, мы получили 3 нужные переменные, которые будем отображать на веб-страничке. П-последний раз я кодировал на Х-Т-М-Л в 1997 году, поэтому извините за олд-олд-стайл из прошлого тысячелетия.
echo ' <div class="top"><strong> <span style="font-size:140%;color:#FF0000">QR</span> <span style="font-size:140%;color:#6666CC">УСЛУГИ</span></strong> <span style="color:#ffffff">BASHNI.ORG</span> <span style="font-size:140%;color:#000000">RUS</span></div> <div class="relative">СЕРТИФИКАТ О ГЕНЕРАЦИИ<br>QR-КОДА-19 <div class="absolute">Действителен</div> <br><p>№ QR9290499831</p></div><div class="info"><br>'.$user.'</div> <div class="info">'.$port.'</div> <div class="info">Дата рождения: '.$birth.'</div> <div class="close">Закрыть</div>';
Стили я опустил, их можно посмотреть на гитхабе. Они тупые и загадочные, как <див>ы из древних сказок Востока.
SwiftUI+MVVM
Переходим к приложению под iOS. Прости, google. Выбираем паттерн проектирования MVVM. Разумеется, для нашей маленькой задачи он избыточен, но потренироваться всегда полезно. Вдруг интервью на сеньора?? Я готов, если что, звоните от $4000. Телефон зашит в статье.
Модель содержит простейшую структуру из строки, которую надо где-то превратить в код-картинку и логическую переменную, говорящую что превращение совершилось/не совершилось.
struct UserModel { var user: User mutating func updateUser(name:String, birth:String, pass:String) { let d = "_" let temp = name+d+birth+d+pass self.user = User(isValid: true, string: temp ) } mutating func cleanUser() { self.user = User(isValid: false, string: "") } struct User { var isValid:Bool var string:String func genUrl()->String { return urlPath+self.string } } }
Полный текст файла ModelView.swift можно посмотреть на гитхабе. Вторая часть нашего паттерна - файл ContentView.swift. Он рисует в одном окне InputView ввод строки и кнопки, а во втором окне QRView изображение QR-кода. Этот SwiftUI стиль и он очень информативен - не нужны XIB-ы, верстка идет прямо в коде в джава-стиле.
import SwiftUI struct ContentView: View { @ObservedObject var model: ModelView var body: some View { if model.isValid() { QRView(model: model) } else { InputView(model: model) } } } struct InputView: View { var model: ModelView @State private var name: String = "" @State private var birth: String = "" @State private var passport: String = "" var body: some View { VStack { Spacer() TextField("3 буквы ФИО", text: $name) TextField("ДР 15.03.1964", text: $birth) TextField("2+3 цифры паспорта", text: $passport) Spacer() HStack { Button("Сделай QR", action: { self.model.tapQRButton(name, birth, passport) }) Spacer() Button("Очистить", action: { name="" birth = "" passport = "" self.model.tapClean() }) }.padding() Spacer() } .padding() .textFieldStyle(RoundedBorderTextFieldStyle()) } } struct QRView: View { var model: ModelView @State private var isSharePresented: Bool = false var body: some View { VStack { if let image = model.generateQRCode() { Text("QR код").bold() Text("успешно сформирован") Spacer() Image(uiImage: image).resizable() .frame(width: 200, height: 200) } else { Text("QR код").bold().foregroundColor(.red) Text("не сформирован").foregroundColor(.red) Spacer() Image(systemName: "qrcode").resizable() .frame(width: 75, height: 75) } Spacer() Button("Назад", action: { self.model.tapClean() } ).padding() }.padding() Spacer() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(model: ModelView()) } }
И, наконец, последний акт Мерлезонского балета - это файл ModelView.swift, который одним глазом глядит на ContentView и получает от него сообщения о нажатии на кнопку, а другим глазом он глядит на UserModel, куда он складывает полученную от ContentView информацию и забирает строку, которую превращает в QR-картинку и возвращает в ContentView.
// // ModelView.swift // import UIKit class ModelView: ObservableObject { @Published private var model:UserModel = UserModel() func isValid()->Bool { return model.user.isValid } func tapQRButton(_ name:String,_ birth:String,_ pass:String) { model.updateUser(name: name, birth: birth, pass: pass) } func tapClean() { model.cleanUser() } func generateQRCode() -> UIImage? { var uiImage: UIImage? // let string = model.user.genUrl() let string = "https://github.com/PapaBubaDiop/qrushka" if let data = string.data(using: String.Encoding.utf8) { if let filter = CIFilter(name: "CIQRCodeGenerator", parameters: ["inputMessage": data, "inputCorrectionLevel": "L"]) { if let outputImage = filter.outputImage, let cgImage = CIContext().createCGImage(outputImage, from: outputImage.extent) { let size = CGSize(width: outputImage.extent.width * 5.0, height: outputImage.extent.height * 5.0) UIGraphicsBeginImageContext(size) if let context = UIGraphicsGetCurrentContext() { context.interpolationQuality = .none context.draw(cgImage, in: CGRect(origin: .zero, size: size)) uiImage = UIGraphicsGetImageFromCurrentImageContext() } UIGraphicsEndImageContext() } } } return uiImage } }
ModelView объявлена внутри ContentView реактивной
@ObservedObject var model: ModelView
и это значит, что графические окна перерисовываются каждый раз когда меняется содержимое нашей модели. Еще раз сценарий нашего приложения
вводим текст и нажимаем кнопку в ContentView
ModelView получает этот текст и обновляет UserModel и значит меняется сам
UserModel формирует из текста url виде строки
раз UserModel изменился, то и ModelView изменился, значит ContentView перерисовывается в соотвествии с новыми данными, которые появились в ModelView
Ура, проект готов и компилируется. No Errors No Warnings.
Запускаем приложение в симуляторе
И получаем стартовый экран. Заполняем поля в соответсвии с документом Джонни.

Нажимаем кнопку Сделай QR и получаем QR-картинку, в которой скрыт линк на наш сайт, где уже лежит php-скрипт, описанный выше. Сформированные картинки, граждане, настоящие!

Вы можете навести свой девайс на картинку и протестировать ее. Мою протестировал тот же охранник из Плазы. Welcome to Russia, Джонни!
Внимание! Никогда так не делайте! Не используйте прототип в незаконных целях! Потому что скоро это будет весить от 3-х до 5-ти.
Заключение
Вся эта история, разумеется, вымышлена от начала и до конца, но код, товарищи, настоящий! Ссылка на гитхаб прилагается.
Задачка
QR-код формируется разной зернистости в зависимости от длины строки. Мое приложение зашифровало в коды число 0 и число 1. Угадайте, где какой QR-код для 0 и 1?

