В первой части этой статьи мы рассмотрели основы работы с
Во второй части статьи я хочу показать, что роль автоматически генерируемых
Создаваемое в этой части статьи приложение с именем
Однако, просто зарегистрировавшись на сайте компании FlightAware, вы можете бесплатно получить временной срез любой интересующей вас информации о рейсах
Код находится на Github.
Наше приложение
На UI нашего приложения мы видим достаточно большой список рейсов различных авиакомпаний с различными аэропортами отправления и назначения, различной дальностью и т.д. В частности в первой строке этого списка представлен рейс
Если вы хотите найти нужные вам рейсы, то в правом верхнем углу экрана имеется маленькая кнопка с именем “Filter”, которая позволит нам фильтровать результаты, например, по тому, откуда
Например, можно выбрать рейсы, которые следуют в международный аэропорт О'Хара в Чикаго и в данный момент находятся в воздухе…
![](https://habrastorage.org/r/w1560/webt/sa/ax/tq/saaxtqjkrbfo9wrewhu8s7px6k4.png)
… или рейсы, выполняемые авиакомпанией
![](https://habrastorage.org/r/w1560/webt/3c/gh/mo/3cghmogfhzpsnvpd8syayca9uts.png)
Полученный список рейсов
![](https://habrastorage.org/r/w1560/webt/4p/ct/oz/4pctoz10p_gcud3nh4ovkmxckvk.png)
Список рейсов
![](https://habrastorage.org/r/w1560/webt/gl/hy/z8/glhyz8tpdqt7pzrny9eiz_dhbwm.png)
![](https://habrastorage.org/r/w1560/webt/hm/0w/mh/hm0wmhtdyuj6pvrsilu99gje8oy.png)
Все эти манипуляции с данными
![](https://habrastorage.org/r/w1560/webt/vn/q2/-z/vnq2-zjdfntazobhsc0vr5qyg6u.png)
Помимо рейсов
![](https://habrastorage.org/r/w1560/webt/cy/ar/y2/cyary2tfmhxojoyznnym3a2vqjy.png)
… и получить о выбранном аэропорте более подробную информацию в виде расположения на карте и табло прилетов и вылетов:
![](https://habrastorage.org/r/w1560/webt/jk/ms/ha/jkmshazeieng7adhhav9otf19lw.png)
Здесь также на помощь нам приходят
![](https://habrastorage.org/r/w1560/webt/9u/ui/gj/9uuigj-_h1zzcrb6twhonisoapk.png)
UI приложения — это здорово! Но, когда вы работаете с базой данных
Вот как выглядит полученная с сайта FlightAware информация о рейсах
![](https://habrastorage.org/r/w1560/webt/ni/eo/qx/nieoqx0kin0-egd-cggibd6mxee.png)
Я хочу разместить эти данные в
Как и в прошлый раз, мы начинаем создание нашего проекта с шаблона, отмечая галочкой опцию Use Core Data при создании нового проекта. Как и в прошлый раз, мы получаем файл Persistence.swift , который отвечает за доступ к
![](https://habrastorage.org/r/w1560/webt/qz/sp/cy/qzspcym4mvaogq-ckrn7xdg07oy.png)
Я не буду подробно останавливаться на том, как создать такую Моделью данных:
![](https://habrastorage.org/r/w1560/webt/o7/yx/ha/o7yxhaekbrch9wigrqqya1chffw.png)
Вы можете это посмотреть в русскоязычном конспекте стэнфордских Лекций CS193P 2020.
Мы видим, что между объектами существуют “взаимосвязи” типа „one to many“ (»один-ко многим") или „one to one“ (»один-к одному"). В частности, для объекта аэропорт
Центральным объектом нашей Модели данных является рейс
рейс Flight
Минимальная информация об аэропортах, но есть информация о географическом положении аэропорта, что в дальнейшем позволит нам разместить их на карте.
аэропорт Airport
авиакомпания Airline
Понятно, что “за кулисами”
![](https://habrastorage.org/r/w1560/webt/c2/a_/nc/c2a_ncwrpurjwr_wz1-yf8hfiwm.png)
Мы будем использовать расширения
Итак, при разработке
Первое, что мы должны сделать, это создать заведомо НЕ-
В расширении
![](https://habrastorage.org/r/w1560/webt/ad/ys/ll/adyslluwzuyev-y-uvktbnbitpu.png)
.......................... .
![](https://habrastorage.org/r/w1560/webt/gq/gu/jx/gqgujx5f0zybk2pteqeciuokwek.png)
Если для аэропорта
Мы могли бы попытаться как-то обработать эту ошибку получше, чем просто заканчивать аварийно приложение, а мы знаем, что восклицательный
Далее класс
![](https://habrastorage.org/r/w1560/webt/xq/9h/nj/xq9hnjr54zhzishzwewrmc26wgw.png)
Но есть еще один кусок в нашей объектно-ориентированной головоломке, это “взаимосвязи” между объектами типа «one-to-many“ (»один-ко многим») или «many-to-many» («многие-ко многим»).
Для объекта
![](https://habrastorage.org/r/w1560/webt/zx/h1/cw/zxh1cwfxpdkqjlnwwjpdsa2vk7k.png)
Эти взаимосвязи,
Вы получаете их либо, имея рейс
![](https://habrastorage.org/r/w1560/webt/hs/q6/rz/hsq6rzaygeawjzpgd4kyekqrwf8.png)
Либо добавляете соответствующий рейс
![](https://habrastorage.org/r/w1560/webt/hm/bw/zs/hmbwzs16wa-bmq9lai8b66nezoo.png)
Если вы добавите рейс
Это очень круто. Все это автоматически настраивается для вас.
Понятно, что как
![](https://habrastorage.org/r/w1560/webt/4g/4b/15/4g4b15zsutcyhncyaheadxfkrhg.png)
...................... .
![](https://habrastorage.org/r/w1560/webt/_m/wn/1v/_mwn1vohdociro08tqdfnormydk.png)
Давайте также избавимся от
У меня есть объект — авиакомпания
![](https://habrastorage.org/r/w1560/webt/ry/5o/5h/ry5o5h4ymrr7tj2kz1bfjllpx6a.png)
.................... .
![](https://habrastorage.org/r/w1560/webt/km/uc/xc/kmucxcvwnzdh4k-5dwmk58bqdlo.png)
Я хочу, чтобы переменные
![](https://habrastorage.org/r/w1560/webt/sq/jb/ox/sqjboxualoi4l5cechgp-vyawj0.png)
Давайте сделаем то же самое с объектом
Я хочу, чтобы переменные
![](https://habrastorage.org/r/w1560/webt/3q/1j/pv/3q1jpvosl40cy1nfe98ru6qpihy.png)
.............. .
![](https://habrastorage.org/r/w1560/webt/g5/r4/lu/g5r4luurbyqgsgjvyphrd6wbsr8.png)
… a также время взлета
![](https://habrastorage.org/r/w1560/webt/vs/ek/t_/vsekt_cw3srwiebeowi96gywccg.png)
Давайте в нашей Модели объектов посмотрим на
![](https://habrastorage.org/r/w1560/webt/jl/8h/6q/jl8h6qvpefpnbk844-izrai6b1u.png)
Это всё, что мы должны были сделать. Теперь наш
То же самое с аэропортами прибытия
Нужно быть осторожным, когда вы проходите через эту технологию избавления от
![](https://habrastorage.org/r/w1560/webt/05/vt/uw/05vtuw6ldwzvedvzi-cuofw-fn8.png)
........................ .
![](https://habrastorage.org/r/w1560/webt/nw/pz/uh/nwpzuhkq6kkcl0qfgper9xtmzue.png)
Потому что запрос
То же самое с дескрипторами сортировки
![](https://habrastorage.org/r/w1560/webt/wt/nv/fi/wtnvfimbsqa8qg-li6zm-6trsqy.png)
.................... .
![](https://habrastorage.org/r/w1560/webt/q5/c2/gb/q5c2gbfm1kcoj9yz2u01qylwdpa.png)
Если у нас нет символа “подчеркивания” “_” в Модели объектов, его не должно быть и в предикате
Итак, мы наделили наши классы class
Но сначала нужно считать данные из файла с
![](https://habrastorage.org/r/w1560/webt/et/h6/pc/eth6pcdb24lthpwcvtzjkcouww4.png)
![](https://habrastorage.org/r/w1560/webt/wu/q5/ku/wuq5kux0uhceqwwnoff_z4tbt0s.png)
Информация о рейсах в сервисе FlightAware выдается для определенного аэропорта по группам: прибывшие
![](https://habrastorage.org/r/w1560/webt/2o/fn/cv/2ofncvvx3g82uudvcrygenz66z4.png)
Вне зависимости от принадлежности к определенной группе, информация о рейсах выдается одинаковая, она скомпонована в структуре
![](https://habrastorage.org/r/w1560/webt/09/un/ix/09unix7nomxvdxslesolz9rson0.png)
Мы читаем
![](https://habrastorage.org/r/w1560/webt/73/rj/pj/73rjpjldo95nc-lonjmcokonxaw.png)
![](https://habrastorage.org/r/w1560/webt/qh/qp/-y/qhqp-ycekqcnnu7v9ksas-bv33m.png)
С помощью этого API мы считываем данные об аэропортах
![](https://habrastorage.org/r/w1560/webt/xx/hk/--/xxhk--cv9un37pgxjhqjw969vay.png)
Считываем данные об авиакомпаниях
![](https://habrastorage.org/r/w1560/webt/t6/en/s8/t6ens8hxnqubmjuzbtnsugmsrwc.png)
Считываем данные о рейсах
В отличие от FlightAware информации об аэропортах и авиакомпаниях, сосредоточенных в отдельных файлах, данные о рейсах, прибывших
![](https://habrastorage.org/r/w1560/webt/d1/xg/ti/d1xgti5ji4g5eztiu-g1btllzoy.png)
Загрузка FlightAware информации в
![](https://habrastorage.org/r/w1560/webt/fj/01/fl/fj01flg5p8zlbcoinfw4b184vsa.png)
Теперь опять вернемся к классам
Вот расширение
![](https://habrastorage.org/r/w1560/webt/05/vt/uw/05vtuw6ldwzvedvzi-cuofw-fn8.png)
........................ .
![](https://habrastorage.org/r/w1560/webt/rh/zs/_r/rhzs_rbt_gnghdoaly4nfpshng0.png)
Вот логика работы этого кода. Если нам удалось получить код Аэропорта
![](https://habrastorage.org/r/w1560/webt/_s/ed/0r/_sed0refwcdt0vjhl3o9iscxx1a.png)
Если в
Пока мы установили не все переменные
Если я вернусь в Модель данных и взгляну на объект
![](https://habrastorage.org/r/w1560/webt/4m/i9/jl/4mi9jle0ytvufloiudwewa2ylkw.png)
Мы установим их со стороны рейса
У класса
![](https://habrastorage.org/r/w1560/webt/05/vt/uw/05vtuw6ldwzvedvzi-cuofw-fn8.png)
........................ .
![](https://habrastorage.org/r/w1560/webt/gw/3r/ar/gw3raradx1ikc90ll-pgfinhx28.png)
В этой функции мы также, как и в случае с аэропортами
Пара — тройка интересных вещей происходит здесь, а именно, когда я устанавливаю некоторые переменные
![](https://habrastorage.org/r/w1560/webt/6e/r_/rz/6er_rznsqsweeu4u8sig1kbduja.png)
… которые со стороны аэропорта
То же самое с “взаимосвязью” c авиакомпанией
![](https://habrastorage.org/r/w1560/webt/bo/ij/kw/boijkwe8bdrdbrqwvuisuktlwe8.png)
Тем самым мы формируем “взаимосвязь”
У класса
![](https://habrastorage.org/r/w1560/webt/w9/dt/6s/w9dt6sdjyr-dftomidkiwldgxu4.png)
В этой функции мы также, как и в случае с аэропортами
Итак, данные закачены в
В нашем проекте будет файл Persistence.swift с точно такой же структурой
![](https://habrastorage.org/r/w1560/webt/cw/ny/yb/cwnyyb5uutbbj7gxp2ptahqi8w4.png)
Там нет переменной
У нас будет очень простой файл приложения CoreDataFlightsApp.swift:
![](https://habrastorage.org/r/w1560/webt/iw/q8/ia/iwq8iaa3hvfyqekmkoelbf1vvis.png)
Топовое
![](https://habrastorage.org/r/w1560/webt/zd/ow/jh/zdowjhrxxrgxrqbujpepucckeku.png)
Давайте последовательно рассмотрим отдельные
![](https://habrastorage.org/r/w1560/webt/jf/7e/ky/jf7ekyr8ke1-mssxjg3ucfkowsq.png)
Наш
![](https://habrastorage.org/r/w1560/webt/hg/hy/ny/hghynyd2kyxkebazvngygfihbkg.png)
… и поисковой строки
![](https://habrastorage.org/r/w1560/webt/x0/4i/d7/x04id7rqorson1ekn2itx0nkncq.png)
… которая создается с помощью модификатора
![](https://habrastorage.org/r/w1560/webt/f-/mj/yg/f-mjygaawz1beuoyl-fjvr_vrvo.png)
… и обрабатывается модификатором
![](https://habrastorage.org/r/w1560/webt/hr/2m/2r/hr2m2r6akhjun6u_cke3clnj4k4.png)
Это дает возможность фильтровать аэропорты по значению первых букв города
Для функционирования
![](https://habrastorage.org/r/w1560/webt/o3/if/bh/o3ifbh9eu9oi43g7sl1se60afli.png)
A изменение поисковой строки
![](https://habrastorage.org/r/w1560/webt/le/47/v3/le47v3zgzxgdpkdcyuiwipwzgnm.png)
Сам предикат поиска
![](https://habrastorage.org/r/w1560/webt/b4/yw/ql/b4ywqljfxg6_4o6ms9dckebb_9i.png)
В результате мы можем задавать в поисковой строке начало названия города, в котором находится аэропорт (например, “San” или “С”), и получать отфильтрованный список таких аэропортов
![](https://habrastorage.org/r/w1560/webt/np/vd/_7/npvd_7aggqsn5zkvqoyvah9wk_w.png)
В отфильтрованном или в НЕ-отфильтрованном списке можно выбрать любой аэропорт, например, аэропорт San Francisco Int’l, и посмотреть более детальную информацию о нем:
![](https://habrastorage.org/r/w1560/webt/ef/gi/9f/efgi9fon_hnqogggw6mqhpmeraw.png)
Давайте посмотрим, как устроен код для просмотра детальной информации об аэропорте
Мы передаем в
Инициируем регион
![](https://habrastorage.org/r/w1560/webt/x6/5l/xn/x65lxnvigqj0fwadrtx1mqlm45w.png)
Предварительно мы сделали так, что объект
![](https://habrastorage.org/r/w1560/webt/i7/2i/wg/i72iwghwl1tmizszu0yuv425jrk.png)
В
![](https://habrastorage.org/r/w1560/webt/tl/yc/4f/tlyc4fxhx6yfvabtorocchaostg.png)
Для этих списков нам даже не нужно делать никакой выборки данных из
![](https://habrastorage.org/r/w1560/webt/64/9x/yt/649xytge-rvulsdey-_fcnlltdk.png)
Но это Objective-C множества
![](https://habrastorage.org/r/w1560/webt/fy/vg/z7/fyvgz7zaodzudgaoi69oddy0tza.png)
В списках
![](https://habrastorage.org/r/w1560/webt/kt/nt/h8/ktnth8ki8ii3edg73uxgx97n49q.png)
Для предварительного просмотра
![](https://habrastorage.org/r/w1560/webt/bg/oq/zy/bgoqzyfuhfxykxnkbidvqsfnkzw.png)
… и пару рейсов:
![](https://habrastorage.org/r/w1560/webt/rb/7b/tv/rb7btvnjkc17nvuu7wkeuuppan4.png)
Для каждого рейса в списках Прилетов и Вылетов выводится краткая информация о рейсе в
![](https://habrastorage.org/r/w1560/webt/g8/wa/yx/g8wayx2dgu6-2_bkrjqnii2ht78.png)
Мы задаем рейс
Если вы внимательно посмотрите на код, то не увидите нигде следов того, что вы общаетесь с
Точно также как и в случае с
![](https://habrastorage.org/r/w1560/webt/hw/3r/91/hw3r91jcgrijkxgn_ucn5umd2wo.png)
![](https://habrastorage.org/r/w1560/webt/ku/qu/fv/kuqufvmpw2_satmahi9vq_gxesg.png)
Наш
![](https://habrastorage.org/r/w1560/webt/9w/6n/uy/9w6nuy8uclji4mwpxalsnkls19o.png)
… который получается с помощью
![](https://habrastorage.org/r/w1560/webt/9h/5l/0t/9h5l0tevf3_7xk6hzomuuq3r1ya.png)
… и поисковой строки
![](https://habrastorage.org/r/w1560/webt/rc/ka/yo/rckayor_tl_bldklchmtsc4zzag.png)
Она задействована в модификаторе
![](https://habrastorage.org/r/w1560/webt/z0/sh/gi/z0shgiotyshaafts-r8menaf-a0.png)
… и обрабатывается модификатором
![](https://habrastorage.org/r/w1560/webt/m4/bm/fn/m4bmfnqhewvho4s1_j3ofgmz9bm.png)
На навигационной панели имеются две кнопки:
![](https://habrastorage.org/r/w1560/webt/3z/2p/dj/3z2pdjgxvwwy5jrkoe3d3j9sovi.png)
![](https://habrastorage.org/r/w1560/webt/6m/ak/to/6makto9__ix--kqgqubzfq4gijg.png)
В результате мы можем задавать в поисковой строке начало названия города, в котором находится аэропорт назначения (например, “San Fr” или “Сhi”), и получить отфильтрованный список рейсов
![](https://habrastorage.org/r/w1560/webt/uu/xp/vw/uuxpvwr30d_stzx5zio4fi5kswq.png)
Мы можем задать более сложный критерий фильтрации, используя при этом структуру
![](https://habrastorage.org/r/w1560/webt/uc/wj/ny/ucwjny9b6hcww6jqwxveohw01em.png)
Критерий фильтрации включает в себя аэропорт назначения
![](https://habrastorage.org/r/w1560/webt/vu/zm/33/vuzm33fjxdwnaxmanv-kw7areoe.png)
Безусловно, есть UI для задания параметров выборки
![](https://habrastorage.org/r/w1560/webt/cy/e4/cx/cye4cxf9eacxq415h0ucd5ziocw.png)
В качестве аэропорта назначения Destination мы выбрали аэропорт Chicago O'Hare, a в качестве авиакомпании Airline — United, кроме того, нас интересуют рейсы, находящиеся в воздухе, то есть переключатель Enroute Only установлен в
В результат применения этого фильтра (кнопка
Если мы переключим Enroute Only в состояние
И опять слева вы видите UI для задания параметров выборки
![](https://habrastorage.org/r/w1560/webt/wh/jz/xn/whjzxnyxldexrxmjiapthdohqxi.png)
Для задания аэропорта назначения Destination используем
![](https://habrastorage.org/r/w1560/webt/zv/t1/zv/zvt1zvvsricgugthfkoaj4s8eeq.png)
Нужный нам аэропорт мы выбираем с помощью
![](https://habrastorage.org/r/w1560/webt/22/w4/nf/22w4nfk09dvisrsiesz_lsysipu.png)
В результате получаем список всех рейсов, направляющихся a аэропорт San Francisco, либо недавно там приземлившихся там:
![](https://habrastorage.org/r/w1560/webt/yj/1f/x-/yj1fx-ompxcu22dnpl9gbuuqclm.png)
Для выбора аэропорта назначения Destination мы можем воспользоваться картой: либо “родной”
![](https://habrastorage.org/r/w1560/webt/rh/0h/rr/rh0hrrfas8aem-vhhdzexh5t8os.png)
Мы можем начать выбирать пункт назначения Destination прямо на карте, a когда увидим нужный нам аэропорт, то просто щелкнем на его индикаторе:
![](https://habrastorage.org/webt/-3/5c/9r/-35c9ru2ryldyyzgexuwbypqb0o.gif)
Мы выбрали аэропорт Los Angeles Int и получили все рейсы, направляющиеся в этот аэропорт:
![](https://habrastorage.org/r/w1560/webt/fd/tg/zo/fdtgzom1n9o_021y72mqjlxr2y8.png)
Каждый отдельный рейс в списке рейсов представлен значительным количеством информации:
![](https://habrastorage.org/r/w1560/webt/n7/ub/ik/n7ubikxxlpcd8kshfomv9j3bgz8.png)
Нужно отметить, что исходные данные, которые мы получаем от FlightAware о рейсах самолетов, имеют единицы измерения отличные от привычных нам СИ единиц измерения. Например, дальность полета представлена в милях (miles), высота в футах (foots), скорость в узлах (knots). Мы, естественно, хотим отображать на нашем UI значения полетных параметров в СИ единицах измерения: дальность и высоту — в километрах km, скорость — в km/h. Для этого нам не нужно писать дополнительный код, так как в
За работу с физическими значениями и их единицами измерения в iOS отвечают объекты
![](https://habrastorage.org/r/w1560/webt/-i/_s/pq/-i_spq9wtsx5ixdy4pvh9ptl0cg.png)
![](https://habrastorage.org/r/w1560/webt/bx/8b/qb/bx8bqblgibrvdfwzrs1pcg8fxms.png)
Это позволит нашей маленькой ViewModel
![](https://habrastorage.org/r/w1560/webt/if/jo/1p/ifjo1pg38piwzn51fi6smxl8yis.png)
… сформировать на нашем UI строки с расстоянием от аэропорта отправления до аэропорта назначения distance = 887 km и скоростью полета по приборам (ППП) speed = 800 km/h, a также с длительностью рейса 1 hours 18 min и средней скоростью полета aveSpeed = 682 km/h в нужных нам единицах измерения. Причем единицы измерения добавляются к нашим строкам со значениями автоматически. Но этого мало. В зависимости от языка, на который настроен ваш iPhone:
![](https://habrastorage.org/r/w1560/webt/tz/mq/hl/tzmqhlwt46ivpnuoqnite6affa8.png)
… единицы измерения указываются на соответствующем языке. Например, если язык вашего iPhone — русский, то и единицы измерения также будут на русском языке:
![](https://habrastorage.org/r/w1560/webt/tu/46/0m/tu460mvzilkoqwxcbwozmfeq57c.png)
Соответственно, если язык вашего iPhone — английский…
![](https://habrastorage.org/r/w1560/webt/yw/78/ea/yw78eav0qdw3el0qkqhzr5kvwbo.png)
… то и единицы измерения также будут на английском языке:
![](https://habrastorage.org/r/w1560/webt/df/w7/mx/dfw7mxatbav47qijyfayjwwgirc.png)
Показана комфортная работа
В нашем приложении мы просто демонстрируем работу с взаимосвязями объектов типа one-to-many, а также динамическую настройку фантастической «обертки»
Мои эксперименты с полётной информацией показали, что
Код находится на Github.
Core Data
в SwiftUI
на примере шаблонного приложения, предложенное Apple. Это было тривиальное приложение, в котором всего лишь один объект Core Data
с одним единственным атрибутом, и тем не менее было показано, что давая объектам Core Data
дополнительную функциональность с помощью „синтаксического сахара“ в расширении extension
их классов class
, автоматически генерируемых Xcode
, можно добиться комфортной работы с Core Data
в SwiftUI
. Эти классы являются миниатюрными ViewModels
для наших SwiftUI Views
, так как они реализуют протоколы ObservableObject
и Identifiable
. И Apple
научила их прекрасно «играть» на поле реактивности SwiftUI. Во второй части статьи я хочу показать, что роль автоматически генерируемых
Xcode
классов class
для объектов Core Data
существенно возрастает при работе с реальными взаимосвязанными объектами — рейсами Flights
, аэропортами Airports
и авиакомпаниями AirLines
, которые мы получаем в интернете на сайте компании FlightAware и размещаем в локальной базе данных Core Data
. Создаваемое в этой части статьи приложение с именем
CoreDataSwiftUIFlights
является сильно упрощенной модификацией реального приложения Enroute из стэнфордских курсов CS193P 2020, которое оперативно подкачивает данные с сервера FlightAware и требует от вас платной подписки на сервис FlightAware . Однако, просто зарегистрировавшись на сайте компании FlightAware, вы можете бесплатно получить временной срез любой интересующей вас информации о рейсах
Flights
, аэропортах Airports
и авиакомпаниях Airlines
в JSON
формате. Эти данные размещаются в Core Data
с учетом взаимосвязей этих объектов, и вы можете не просто видеть всю информацию о рейсах, но и делать различные запросы к ней с помощью фильтров и сортировать ее нужным вам способом.![](https://habrastorage.org/webt/uv/9x/sk/uv9xsks6w1taoephiojrt2lvyzw.png)
Код находится на Github.
МОДЕЛЬ ДАННЫХ в CORE DATA
Наше приложение
CoreDataSwiftUIFlights
загружает рейсы, связанные с международным аэропортом Сан-Франциско KSFO и международным аэропортом О'Хара в Чикаго KORD. Это коды аэропортов. На UI нашего приложения мы видим достаточно большой список рейсов различных авиакомпаний с различными аэропортами отправления и назначения, различной дальностью и т.д. В частности в первой строке этого списка представлен рейс
United Air Lines UAL412
, который летит из Сан-Франциско в Мехико. Он недавно вылетел, пролетев всего 3% пути длиною 3032 км, и прибудет в Мехико приблизительно в 11:10. Если вы хотите найти нужные вам рейсы, то в правом верхнем углу экрана имеется маленькая кнопка с именем “Filter”, которая позволит нам фильтровать результаты, например, по тому, откуда
Origin
и куда Destination
летит самолет, какой авиакомпании Airline
и находится самолет в воздухе в данный момент или нет Enroute only
, потому что есть рейсы, которые запланированы на прибытие в определенное время, но, возможно, они ещё не взлетели или только что прилетели и уже находятся на земле.Например, можно выбрать рейсы, которые следуют в международный аэропорт О'Хара в Чикаго и в данный момент находятся в воздухе…
![](https://habrastorage.org/webt/sa/ax/tq/saaxtqjkrbfo9wrewhu8s7px6k4.png)
… или рейсы, выполняемые авиакомпанией
United Air Lines
и находящиеся в данный момент в воздухе:![](https://habrastorage.org/webt/3c/gh/mo/3cghmogfhzpsnvpd8syayca9uts.png)
Полученный список рейсов
Flights
можно фильтровать по первым буквам названия города прибытия, вводимым в поисковой строке, если аэропорт прибытия Destination
не задан жестко в критерии фильтрации:![](https://habrastorage.org/webt/4p/ct/oz/4pctoz10p_gcud3nh4ovkmxckvk.png)
Список рейсов
Flights
, полученный по какому угодно критерию, можно сортировать по расстоянию Distance
между аэропортами отправления и прибытия и по времени прибытия ActualOn
(реального или приблизительного):![](https://habrastorage.org/webt/gl/hy/z8/glhyz8tpdqt7pzrny9eiz_dhbwm.png)
![](https://habrastorage.org/webt/hm/0w/mh/hm0wmhtdyuj6pvrsilu99gje8oy.png)
Все эти манипуляции с данными
Core Data
возможны благодаря всего двум элементам SwiftUI
: @FetchRequest
и View
модификатору onChange
:![](https://habrastorage.org/webt/vn/q2/-z/vnq2-zjdfntazobhsc0vr5qyg6u.png)
Помимо рейсов
Flights
, нам предоставляется список аэропортов Airports
, который мы также можем фильтровать по первым буквам названия города, в котором находится аэропорт:![](https://habrastorage.org/webt/cy/ar/y2/cyary2tfmhxojoyznnym3a2vqjy.png)
… и получить о выбранном аэропорте более подробную информацию в виде расположения на карте и табло прилетов и вылетов:
![](https://habrastorage.org/webt/jk/ms/ha/jkmshazeieng7adhhav9otf19lw.png)
Здесь также на помощь нам приходят
@FetchRequest
и View
модификатор onChange
:![](https://habrastorage.org/webt/9u/ui/gj/9uuigj-_h1zzcrb6twhonisoapk.png)
UI приложения — это здорово! Но, когда вы работаете с базой данных
Core Data
, главное — это Модель данных с объектами и их атрибутами, а для этого мы должны знать, какая информация доступна нам на сайте компании FlightAware.Работа с полетами Flights, аэропортами Airports и авиакомпаниями AirLines из FlightAware в базе данных Core Data
.Вот как выглядит полученная с сайта FlightAware информация о рейсах
Flights
, аэропортах Airports
и авиакомпаниях Airlines
в JSON
формате:![](https://habrastorage.org/webt/ni/eo/qx/nieoqx0kin0-egd-cggibd6mxee.png)
Я хочу разместить эти данные в
Core Data
и буду отображать их в виде списков аэропортов, авиакомпаний и рейсов, позволяя пользователю фильтровать эти списки по определенным критериям.Как и в прошлый раз, мы начинаем создание нашего проекта с шаблона, отмечая галочкой опцию Use Core Data при создании нового проекта. Как и в прошлый раз, мы получаем файл Persistence.swift , который отвечает за доступ к
Core Data
и который мы модифицируем также, как в нашем прошлом шаблонном приложении, но с другой Моделью данных:![](https://habrastorage.org/webt/qz/sp/cy/qzspcym4mvaogq-ckrn7xdg07oy.png)
Я не буду подробно останавливаться на том, как создать такую Моделью данных:
![](https://habrastorage.org/webt/o7/yx/ha/o7yxhaekbrch9wigrqqya1chffw.png)
Вы можете это посмотреть в русскоязычном конспекте стэнфордских Лекций CS193P 2020.
Мы видим, что между объектами существуют “взаимосвязи” типа „one to many“ (»один-ко многим") или „one to one“ (»один-к одному"). В частности, для объекта аэропорт
Airport
— это рейсы flightsFrom_
, которые отправляются с этого аэропорта, и рейсы flightsTo_
, которые прибывают в этот аэропорт, а для объекта авиакомпания Airline
— это обслуживаемые ей рейсы flights_
. Центральным объектом нашей Модели данных является рейс
Flight
, у которого есть аэропорт отправления destination
и аэропорт прибытия origin
, а также авиакомпания airline
, выполняющая этот рейс. Поэтому у нас есть объект аэропорт Airport
и объект авиакомпания Airline
. Кроме того, у объекта рейс Flight
множество атрибутов с полётной информацией в единицах измерения, которые не соответствуют привычной нам метрической системе мер СИ:рейс Flight
actualIn | Реальное время прибытия к гейту | |
actualOff | Реальное время вылета | |
actualOn | Реальное время прибытия на взлетную полосу | |
aircraftType_ | Тип самолета | |
estimatedIn | Приблизительное время прибытия к гейту | |
estimatedOff | Приблизительное время вылета | |
estimatedOn_ | Приблизительное время прибытия на взлетную полосу | |
filedAirspeed_ | Скорость полета по ППП | knots (узлы) |
filedAltitude | Высота полета по ППП (100 футов) | 100s foots (сотни футов) |
ident_ | идентификатор рейса | |
progressPercent | Процент выполнения рейса, основанный на вылете/прибытии на взлетно-посадочной полосе. | % |
routeDistance | Запланированное расстояние на основе указанного маршрута. Может отличаться от фактического расстояния. | statute miles (мили) |
scheduledIn | Время прибытия к гейту по расписанию | |
scheduledOff_ | Время вылета по расписанию | |
scheduledOn_ | Время прибытия на взлетную полосу по расписанию | |
status_ | Статус рейса | |
ВЗАИМОСВЯЗИ | ----------------------рейса Flight --------------------- | |
airline_ | Авиакомпания Airline | one-to-one |
destination_ | Аэропорт назначения Airport | one-to-one |
origin_ | Аэропорт отправления Airport | one-to-one |
Минимальная информация об аэропортах, но есть информация о географическом положении аэропорта, что в дальнейшем позволит нам разместить их на карте.
аэропорт Airport
city_ | Город | |
countryCode | Код Страны | |
icao_ | Код аэропорта | |
latitude | географическая широта | |
location | местоположение | |
longitude | географическая долгота | |
name | название аэропорта | |
state | штат | |
timezone | временной пояс | |
ВЗАИМОСВЯЗИ | -------------------аэропорта Airport------------------- | |
flightsfrom_ | вылеты | one-to-many |
flightsto_ | прилеты | one-to-many |
авиакомпания Airline
code_ | код авиакомпании | |
name_ | название авиакомпании | |
shortname_ | краткое название авиакомпании | |
ВЗАИМОСВЯЗИ | -------------------аэропорта Airline------------------- | |
flights_ | рейсы авиакомпании | one-to-many |
Понятно, что “за кулисами”
Xcode
сгенерирует для Core Data
объектов классы class
Airport
, Airline
и Flight
, если у нас указана опция “Class Definition”:![](https://habrastorage.org/webt/c2/a_/nc/c2a_ncwrpurjwr_wz1-yf8hfiwm.png)
Мы будем использовать расширения
extension
этих классов class
для того, чтобы продемонстрировать вам еще одну функцию мини-ViewModels
, которыми являются эти классы class
объектов Core Data
, и которая состоит в преобразованиях данных Модели к удобному для SwiftUI Views
виду. А именно мы будем преобразовывать полётную информацию в метрическую систему координат СИ. За работу с физическими значениями и их единицами измерения в iOS отвечают объекты Measurement
— это отдельная тема, которой мы чуть-чуть коснемся ниже и о которой можно почитать здесь. Итак, при разработке
SwiftUI
приложений c Core Data
мы фокусируемся на расширениях extension
классов class
Core Data
: Flight
, Airport
и AirLine
. Мы должны «запрятать» туда все, с чем SwiftUI Views
будет не комфортно работать.Расширение extension класса Airport.
Первое, что мы должны сделать, это создать заведомо НЕ-
Optional
атрибуты объекта Airport
, такие как код icao
и город расположения аэропорта city
. Как было сказано в первой части статьи, мы оставляем Core Data
комфортную для нее работу с Optional
атрибутами, а сами формируем нужные нам НЕ-Optional
атрибуты как вычисляемые переменные var
с get{}
и set {}
. В расширении
extension
класса Airport
мы создадим аналоги задействованных в Моделе Данных Core Data
атрибутовicao_
(код аэропорта) и city_
(город расположения) с символом “подчеркивания” “_” в конце имени. Это их НЕ-Optional
версии, которые мы оформляем в виде вычисляемых переменные var
icao
и var
city
:![](https://habrastorage.org/webt/ad/ys/ll/adyslluwzuyev-y-uvktbnbitpu.png)
.......................... .
![](https://habrastorage.org/webt/gq/gu/jx/gqgujx5f0zybk2pteqeciuokwek.png)
Если для аэропорта
Airport
атрибут или переменная var
icao_
будет равна nil
, то это будет действительно ошибка, её не должно быть. Когда я создаю аэропорт Airport
, то первое, что я делаю буквально на следующей строке кода, это устанавливаю свойство icao
, так что оно никогда не равно nil
. Если это происходит, то явно произошла какая-то ошибка в данных.Мы могли бы попытаться как-то обработать эту ошибку получше, чем просто заканчивать аварийно приложение, а мы знаем, что восклицательный
!
знак в случае ошибки просто “обрушивает” ваше приложение. Если вы находитесь в процессе разработки приложения, то обрушение приложения поможет обнаружить такие случаи повреждения базы данных.Далее класс
class
Airport
является Identifiable
, и мы в качестве переменной var
id
предлагаем использовать код аэропорта icao
. Я также сделала объект Airport
Comparable
, это позволяет сортировать аэропорты:![](https://habrastorage.org/webt/xq/9h/nj/xq9hnjr54zhzishzwewrmc26wgw.png)
Но есть еще один кусок в нашей объектно-ориентированной головоломке, это “взаимосвязи” между объектами типа «one-to-many“ (»один-ко многим») или «many-to-many» («многие-ко многим»).
Для объекта
Airport
это рейсы flightsFrom
, которые отправляются, и flightsTo
, которые прибывают в этот аэропорт:![](https://habrastorage.org/webt/zx/h1/cw/zxh1cwfxpdkqjlnwwjpdsa2vk7k.png)
Эти взаимосвязи,
flightsFrom
и flightsTo
, достаточно установить с одной из сторон.Вы получаете их либо, имея рейс
Flight
с аэропортами назначения destination
и отправления origin
, и это автоматически формирует множества NSSet
для flightsFrom
и flightsTo
для аэропортов Airport
. ![](https://habrastorage.org/webt/hs/q6/rz/hsq6rzaygeawjzpgd4kyekqrwf8.png)
Либо добавляете соответствующий рейс
Flight
непосредственно к множествам flightsFrom
и flightsTo
. ![](https://habrastorage.org/webt/hm/bw/zs/hmbwzs16wa-bmq9lai8b66nezoo.png)
Если вы добавите рейс
Flight
к flightsTo
на стороне аэропорта Airport
, это автоматически заставит этот рейс Flight
указать на этот аэропорт Airport
в качестве пункта назначения destination
.Это очень круто. Все это автоматически настраивается для вас.
Понятно, что как
Swift
, так и SwiftUI
удобнее взаимодействовать со своими множествами Set<Flight>
, a не осуществлять каждый раз приведение ТИПа NSSet as? Set<Flight >
, поэтому мы используем “синтаксический сахар” для получения переменных var
flightsTo
и var
flightsFrom
в виде Set<Flight >
, a в Модели данных “взаимосвязи” опять обозначим теми же именами с символом “подчеркиванием” “_” ( в автоматически генерируемых классах class они будут NSSet
):![](https://habrastorage.org/webt/4g/4b/15/4g4b15zsutcyhncyaheadxfkrhg.png)
...................... .
![](https://habrastorage.org/webt/_m/wn/1v/_mwn1vohdociro08tqdfnormydk.png)
Расширение extension классов Airline и Flight.
Давайте также избавимся от
Optional
и для других объектов в нашей базе данных.У меня есть объект — авиакомпания
Airline
, и я сделала для этого объекта те же самые вещи, что и для объекта Airport
:![](https://habrastorage.org/webt/ry/5o/5h/ry5o5h4ymrr7tj2kz1bfjllpx6a.png)
.................... .
![](https://habrastorage.org/webt/km/uc/xc/kmucxcvwnzdh4k-5dwmk58bqdlo.png)
Я хочу, чтобы переменные
vars
code
, name
, shortname
были НЕ Optional
и, конечно, переменная flights
была бы Swift
множеством Set
. Так что code
, name
, shortname
и flights
, то есть все переменные в объекте Airline
, мы должны переименовать, добавив в конце имени символом “подчеркивания” “_”:![](https://habrastorage.org/webt/sq/jb/ox/sqjboxualoi4l5cechgp-vyawj0.png)
Давайте сделаем то же самое с объектом
Flight
. Я хочу, чтобы переменные
var
идентификатор ident
, аэропорт назначения destination
, аэропорт отправления origin
, тип самолета aircraftType
были НЕ Optional
: ![](https://habrastorage.org/webt/3q/1j/pv/3q1jpvosl40cy1nfe98ru6qpihy.png)
.............. .
![](https://habrastorage.org/webt/g5/r4/lu/g5r4luurbyqgsgjvyphrd6wbsr8.png)
… a также время взлета
sheduledOff
и посадки sheduledOn
по расписанию, приблизительное время прибытия estimatedOn
, скорость filedAirspeed
, статус state
, авиакомпания airline
: ![](https://habrastorage.org/webt/vs/ek/t_/vsekt_cw3srwiebeowi96gywccg.png)
Давайте в нашей Модели объектов посмотрим на
Flight
и добавим символ “подчеркивания” “_” в конце имён всех этих переменных vars
ident
, destination
, origin
, aircraftType
, sheduledOff
, sheduledOn
, estimatedOn
, filedAirspeed
, state
, airline
. Сами же вычисляемые переменные (без символа “подчеркивания”) не будут у нас равняться nil
.![](https://habrastorage.org/webt/jl/8h/6q/jl8h6qvpefpnbk844-izrai6b1u.png)
Это всё, что мы должны были сделать. Теперь наш
SwiftUI
код будет выглядеть намного лучше. Нам не нужно постоянно проверять, что ident
не равен nil
, потому что идентификатор ident
рейса Flight
никогда не может быть равен nil
. Если у рейса Flight
нет ident
, то, фактически, этот рейс не существует.То же самое с аэропортами прибытия
destination
и отправления origin
, рейсы Flight
обязаны также иметь по крайней мере время взлета sheduledOff
и посадки sheduledOn
по расписанию, а также примерное время прибытия estimatedOn
.Нужно быть осторожным, когда вы проходите через эту технологию избавления от
Optional
атрибутов с помощью вычисляемых переменных. Потому что если вы делаете выборку с помощью fetch
, как мы делали выборку аэропортов Airport
, то в предикате predicate
должна быть версия с символом “подчеркивания” “_” — icao_
:![](https://habrastorage.org/webt/05/vt/uw/05vtuw6ldwzvedvzi-cuofw-fn8.png)
........................ .
![](https://habrastorage.org/webt/nw/pz/uh/nwpzuhkq6kkcl0qfgper9xtmzue.png)
Потому что запрос
request
и его предикат predicate
выполняются по полям объекта в базе данных, а не по переменным vars
, которые находятся в нашем коде. Это реальная выборка в базе данных и там должны быть имена полей в базе данных.То же самое с дескрипторами сортировки
NSSortDescriptor
. У нас используется name_
, и если мы посмотрим на объект Airline
в Модели объектов, то увидим, что там присутствует именно name_
c символом “подчеркивания” “_”:![](https://habrastorage.org/webt/wt/nv/fi/wtnvfimbsqa8qg-li6zm-6trsqy.png)
.................... .
![](https://habrastorage.org/webt/q5/c2/gb/q5c2gbfm1kcoj9yz2u01qylwdpa.png)
Если у нас нет символа “подчеркивания” “_” в Модели объектов, его не должно быть и в предикате
predicate
, и в сортировке NSSortDescriptors
. Итак, мы наделили наши классы class
Airport
, Airline
и Flight
многими функциональными возможностями, но пока у нас нет самого главного — записи данных FlightAware в Core Data
. Запись FlightAware данных в Core Data.
Но сначала нужно считать данные из файла с
JSON
данными. Для этого создадим Модель данных для информации, получаемой из сервиса FlightAware. Это те же объекты: аэропорт AirportInfo
, авиакомпания AirlineInfo
и рейсы FlightsInfo
, но только настроенные на чтение JSON
данных:![](https://habrastorage.org/webt/et/h6/pc/eth6pcdb24lthpwcvtzjkcouww4.png)
![](https://habrastorage.org/webt/wu/q5/ku/wuq5kux0uhceqwwnoff_z4tbt0s.png)
Информация о рейсах в сервисе FlightAware выдается для определенного аэропорта по группам: прибывшие
arrivals
, вылетевшие departures
, прибывающие по расписанию scheduledArrivals
, вылетающие по расписанию scheduledDepartures
:![](https://habrastorage.org/webt/2o/fn/cv/2ofncvvx3g82uudvcrygenz66z4.png)
Вне зависимости от принадлежности к определенной группе, информация о рейсах выдается одинаковая, она скомпонована в структуре
struct
Arrival
:![](https://habrastorage.org/webt/09/un/ix/09unix7nomxvdxslesolz9rson0.png)
Мы читаем
JSON
файлы с помощью очень простого API на основе Combine
и специально настроенного декодера jsonDecoder
, который считывает даты и время в ISO8601 формате, a также обеспечивает преобразование имен из формата Snake case. Код размещен в файле FromJSONAPI.swift …![](https://habrastorage.org/webt/73/rj/pj/73rjpjldo95nc-lonjmcokonxaw.png)
![](https://habrastorage.org/webt/qh/qp/-y/qhqp-ycekqcnnu7v9ksas-bv33m.png)
С помощью этого API мы считываем данные об аэропортах
[AirportInfo]
из файла с именем AIRPORTS.json и размещаем с помощью функции update
в объектах Core Data
с именем Airport
:![](https://habrastorage.org/webt/xx/hk/--/xxhk--cv9un37pgxjhqjw969vay.png)
Считываем данные об авиакомпаниях
[AirlineInfo]
из файла AIRLINES.json и размещаем с помощью своей функции update
в объектах Core Data
с именем Airline
:![](https://habrastorage.org/webt/t6/en/s8/t6ens8hxnqubmjuzbtnsugmsrwc.png)
Считываем данные о рейсах
FlightInfo
из файла SFO.json, который соответствует вполне определенному аэропорту, в данном случае SFO (San Francisco Int ), и размещаем с помощью своей функции update
в объектах Core Data
с именем Flight
.В отличие от FlightAware информации об аэропортах и авиакомпаниях, сосредоточенных в отдельных файлах, данные о рейсах, прибывших
arrivals
, покинувших departures
, прибывающих по расписанию scheduledArrivals
и убывающих по расписанию scheduledDepartures
, сосредоточены в файлах соответствующих определенным аэропортам:![](https://habrastorage.org/webt/d1/xg/ti/d1xgti5ji4g5eztiu-g1btllzoy.png)
Загрузка FlightAware информации в
Core Data
осуществляется с помощью класса class
LoadFlights
и его метода load()
из файлов с именами AIRPORT.json, AIRLINE.json и SFO.json: ![](https://habrastorage.org/webt/fj/01/fl/fj01flg5p8zlbcoinfw4b184vsa.png)
Теперь опять вернемся к классам
class
объектов Core Data
, сгенерированным для нас Xcode
, и разместим там static
функции func
update
для записи FlightAware информации в соответствующие объекты Core Data
. Вот расширение
extension
класса class
Airport
с функцией update
, записывающей AirportInfo
в Core Data
:![](https://habrastorage.org/webt/05/vt/uw/05vtuw6ldwzvedvzi-cuofw-fn8.png)
........................ .
![](https://habrastorage.org/webt/rh/zs/_r/rhzs_rbt_gnghdoaly4nfpshng0.png)
Вот логика работы этого кода. Если нам удалось получить код Аэропорта
icao
из FlightAware информации info
, то будем искать аэропорт airport
с этим кодом icao
с помощью функции func
withICAO
, находящейся в том же расширении extension
класса class
Airport
. Обратите внимание, что при выборке данных из Core Data
в предикате мы всегда используем имя icao_
с “подчеркиванием” “_”, если это НЕ-Optional
атрибут:![](https://habrastorage.org/webt/_s/ed/0r/_sed0refwcdt0vjhl3o9iscxx1a.png)
Если в
Core Data
уже есть аэропорт airport
с этим кодом icao
, то возвращаем его в функцию update
, если нет, то создаем новый аэропорт airport
с заданным в FlightAware кодом аэропорта icao
и также возвращаем его в функцию update
для обновления остальных атрибутов.Пока мы установили не все переменные
var
, которые есть в объекте Airport
.Если я вернусь в Модель данных и взгляну на объект
Airport
, то увижу все переменные, которые мы установили, кроме двух переменные var
flightsFrom
и flightsTo
, которые представляют собой взаимосвязи с объектом Flight
:![](https://habrastorage.org/webt/4m/i9/jl/4mi9jle0ytvufloiudwewa2ylkw.png)
Мы установим их со стороны рейса
Flight
, когда будем заполнять информацию о рейсе Flight
и определим для него соответствующие аэропорты Airport
назначения destination
и прибытия origin
.У класса
class
рейс Flight
есть своя static
функцию func
update
для записи информации FlightInfo
FlightAware в Core Data
:![](https://habrastorage.org/webt/05/vt/uw/05vtuw6ldwzvedvzi-cuofw-fn8.png)
........................ .
![](https://habrastorage.org/webt/gw/3r/ar/gw3raradx1ikc90ll-pgfinhx28.png)
В этой функции мы также, как и в случае с аэропортами
Airport
, ищем рейс flight
с нужным идентификатором ident_
(мы всегда используем имя с “подчеркиванием” “_” ). Находим в Core Data
этот рейс или создаем новый рейс Flight (context:context)
. В любом случае обновляем его в соответствии с указанной информацией из FlightAware. Ну, а далее следует код, который мы уже видели прежде.Пара — тройка интересных вещей происходит здесь, а именно, когда я устанавливаю некоторые переменные
var
“взаимосвязей”. Здесь я устанавливаю аэропорт отправления origin
и аэропорт назначения destination
с теми кодами icao
, какие указаны для этого рейса, путем поиска уже существующего в Core Data
или создания нового аэропорта Airport
. Тем самым мы формируем “взаимосвязи” Flight - Airport
в виде origin_
и destination_
:![](https://habrastorage.org/webt/6e/r_/rz/6er_rznsqsweeu4u8sig1kbduja.png)
… которые со стороны аэропорта
Airport - Flight
превращаются в недостающие “взаимосвязи” flightsTo_
и flightsFrom_
для объекта аэропорт Airport
. Все это делается автоматически.То же самое с “взаимосвязью” c авиакомпанией
Airline- Flight
. В FlightAware информации о рейсе faflight
указывается код авиакомпании airlineCode
. Используя его и функцию func
Airline.withCode
мы находим нужную авиакомпанию в Core Data
или создаем новую авиакомпанию airline_
для нашего рейса Flight
:![](https://habrastorage.org/webt/bo/ij/kw/boijkwe8bdrdbrqwvuisuktlwe8.png)
Тем самым мы формируем “взаимосвязь”
Flight - Airline
в виде airline_
, которая со стороны авиакомпании Airline
превращается в недостающую “взаимосвязь” flights_
. У класса
class
авиакомпании Airline
есть своя static
функцию func
update
для записи FlightAware информации AirlineInfo
в Core Data
:![](https://habrastorage.org/webt/w9/dt/6s/w9dt6sdjyr-dftomidkiwldgxu4.png)
В этой функции мы также, как и в случае с аэропортами
Airport
, ищем авиакомпанию с нужным кодом code_
(мы всегда используем имя с “подчеркиванием” “_” ). Находим в Core Data
эту авиакомпанию или, если она отсутствует. то создаем новую авиакомпанию Airline (context:context)
, a затем обновляем её в соответствии с информацией из FlightAware.ИНТЕРФЕЙС в SwiftUI.
Итак, данные закачены в
Core Data
. Пришло время проектировать UI в SwiftUI
.В нашем проекте будет файл Persistence.swift с точно такой же структурой
struct
PersistenceController
, как и в нашем предыдущем шаблонном приложении:![](https://habrastorage.org/webt/cw/ny/yb/cwnyyb5uutbbj7gxp2ptahqi8w4.png)
Там нет переменной
var
preview
, в которой формируются данные для предварительного просмотра Preview и которая присутствует в шаблоне, мы будем формировать их локально для каждого отдельного View
.У нас будет очень простой файл приложения CoreDataFlightsApp.swift:
![](https://habrastorage.org/webt/iw/q8/ia/iwq8iaa3hvfyqekmkoelbf1vvis.png)
Топовое
HomeView
предоставляет возможность работы с Core Data
данными: со списком рейсов FlightsView
, аэропортов AirportsView
и авиалиний AirlinesView
:![](https://habrastorage.org/webt/zd/ow/jh/zdowjhrxxrgxrqbujpepucckeku.png)
АЭРОПОРТЫ AirportsView
Давайте последовательно рассмотрим отдельные
View
и начнем с AirportsView
:![](https://habrastorage.org/webt/jf/7e/ky/jf7ekyr8ke1-mssxjg3ucfkowsq.png)
Наш
View
состоит из списка аэропортов airports
, который получается с помощью @FetchRequest
:![](https://habrastorage.org/webt/hg/hy/ny/hghynyd2kyxkebazvngygfihbkg.png)
… и поисковой строки
query
:![](https://habrastorage.org/webt/x0/4i/d7/x04id7rqorson1ekn2itx0nkncq.png)
… которая создается с помощью модификатора
.searchable
:![](https://habrastorage.org/webt/f-/mj/yg/f-mjygaawz1beuoyl-fjvr_vrvo.png)
… и обрабатывается модификатором
.onChange(of: query)
:![](https://habrastorage.org/webt/hr/2m/2r/hr2m2r6akhjun6u_cke3clnj4k4.png)
Это дает возможность фильтровать аэропорты по значению первых букв города
city_
, в котором находится аэропорт.Для функционирования
@FetchRequest
требуется контекст managedObjectContext
, и мы получаем его с помощью @Environment (\.managedObjectContext)
в виде переменной var
viewContext
:![](https://habrastorage.org/webt/o3/if/bh/o3ifbh9eu9oi43g7sl1se60afli.png)
A изменение поисковой строки
query
приводит к настройке предиката nsPredicate
, лежащего в основе @FetchRequest
:![](https://habrastorage.org/webt/le/47/v3/le47v3zgzxgdpkdcyuiwipwzgnm.png)
Сам предикат поиска
searchPredicate
находится в классе class
Airport
:![](https://habrastorage.org/webt/b4/yw/ql/b4ywqljfxg6_4o6ms9dckebb_9i.png)
В результате мы можем задавать в поисковой строке начало названия города, в котором находится аэропорт (например, “San” или “С”), и получать отфильтрованный список таких аэропортов
Airport
:![](https://habrastorage.org/webt/np/vd/_7/npvd_7aggqsn5zkvqoyvah9wk_w.png)
В отфильтрованном или в НЕ-отфильтрованном списке можно выбрать любой аэропорт, например, аэропорт San Francisco Int’l, и посмотреть более детальную информацию о нем:
- Полное название аэропорта,
- Расположение на карте,
- Прилеты и Вылеты (это наши
flightsTo
иflightsFrom
)
![](https://habrastorage.org/webt/ef/gi/9f/efgi9fon_hnqogggw6mqhpmeraw.png)
Давайте посмотрим, как устроен код для просмотра детальной информации об аэропорте
AirportDetailView
. Мы передаем в
AirportDetailView
выбранный в предыдущем списке аэропорт var
airport: Airport
, благодаря чему нам даже не нужен контекст managedObjectContext
. Кроме того, нам нужна @State
переменная var
to
для сегментов Прилеты и Вылеты. Инициируем регион
mapRegion
для отображения аэропорта airport
на карте с помощью Map
:![](https://habrastorage.org/webt/x6/5l/xn/x65lxnvigqj0fwadrtx1mqlm45w.png)
Предварительно мы сделали так, что объект
Airport
базы данных Core Data
также реализует протокол MKAnnotation
, что позволит нам без труда отображать его на карте Map
:![](https://habrastorage.org/webt/i7/2i/wg/i72iwghwl1tmizszu0yuv425jrk.png)
В
body
мы размещаем VStack
с картой Map
, c Picker
для выбора сегментов Прилеты и Вылеты и список Прилетов или список Вылетов в зависимости от того, какой сегмент выбирает пользователь:![](https://habrastorage.org/webt/tl/yc/4f/tlyc4fxhx6yfvabtorocchaostg.png)
Для этих списков нам даже не нужно делать никакой выборки данных из
Core Data
. Благодаря связям one-to-many между объектами Flight
и Airport
, эти списки формируются автоматически в виде множеств flightsFrom_
и flightsTo_
:![](https://habrastorage.org/webt/64/9x/yt/649xytge-rvulsdey-_fcnlltdk.png)
Но это Objective-C множества
NSSet
, которые мы с помощью вычисляемых переменных flightsFrom
и flightsTo
превратили в Swift множества Set<Flight>
в расширении extension
класса class
Airport
:![](https://habrastorage.org/webt/fy/vg/z7/fyvgz7zaodzudgaoi69oddy0tza.png)
В списках
List
Прилетов и Вылетов мы используем массив Прилетов Array(airport.flightsTo)
и массив Вылетов Array(airport.flightsTo)
, которые отсортированы соответственно по времени прилета и вылета:![](https://habrastorage.org/webt/kt/nt/h8/ktnth8ki8ii3edg73uxgx97n49q.png)
Для предварительного просмотра
AirportDetailView_Preview
мы сформируем базу данных Core Data
“в памяти” (in memory) и добавим туда тройку аэропортов:![](https://habrastorage.org/webt/bg/oq/zy/bgoqzyfuhfxykxnkbidvqsfnkzw.png)
… и пару рейсов:
![](https://habrastorage.org/webt/rb/7b/tv/rb7btvnjkc17nvuu7wkeuuppan4.png)
Для каждого рейса в списках Прилетов и Вылетов выводится краткая информация о рейсе в
FlightViewShort
:![](https://habrastorage.org/webt/g8/wa/yx/g8wayx2dgu6-2_bkrjqnii2ht78.png)
Мы задаем рейс
flight
и аэропорт airport
, и в зависимости от того, является ли этот аэропорт airport пунктом отправления origin
или пунктом назначения destination
, мы создаем нужный UI.Если вы внимательно посмотрите на код, то не увидите нигде следов того, что вы общаетесь с
Core Data
, это просто объекты: рейс var
flight : Flight
и аэропорт var
airport: Airport
, и мы соответствующим образом форматируем информацию о них, размещая на нашем UI время прилета / вылета по расписанию и реальное время прилета / вылета, куда прилетает или откуда улетает самолет.Точно также как и в случае с
AirportDetailView
, мы формируем для предварительного просмотра Preview базу данных Core Data
“в памяти” (in memory) и добавляем туда пару аэропортов и рейс:![](https://habrastorage.org/webt/hw/3r/91/hw3r91jcgrijkxgn_ucn5umd2wo.png)
РЕЙСЫ FlightsView
![](https://habrastorage.org/webt/ku/qu/fv/kuqufvmpw2_satmahi9vq_gxesg.png)
Наш
View
состоит из списка рейсов flights
:![](https://habrastorage.org/webt/9w/6n/uy/9w6nuy8uclji4mwpxalsnkls19o.png)
… который получается с помощью
@FetchRequest
:![](https://habrastorage.org/webt/9h/5l/0t/9h5l0tevf3_7xk6hzomuuq3r1ya.png)
… и поисковой строки
query
:![](https://habrastorage.org/webt/rc/ka/yo/rckayor_tl_bldklchmtsc4zzag.png)
Она задействована в модификаторе
.searchable
:![](https://habrastorage.org/webt/z0/sh/gi/z0shgiotyshaafts-r8menaf-a0.png)
… и обрабатывается модификатором
.onChange(of: query)
:![](https://habrastorage.org/webt/m4/bm/fn/m4bmfnqhewvho4s1_j3ofgmz9bm.png)
На навигационной панели имеются две кнопки:
Load
и Filter
:![](https://habrastorage.org/webt/3z/2p/dj/3z2pdjgxvwwy5jrkoe3d3j9sovi.png)
![](https://habrastorage.org/webt/6m/ak/to/6makto9__ix--kqgqubzfq4gijg.png)
В результате мы можем задавать в поисковой строке начало названия города, в котором находится аэропорт назначения (например, “San Fr” или “Сhi”), и получить отфильтрованный список рейсов
flights
по названию города аэропорта назначения. В нашем случае это San Francisco и Chicago:![](https://habrastorage.org/webt/uu/xp/vw/uuxpvwr30d_stzx5zio4fi5kswq.png)
Мы можем задать более сложный критерий фильтрации, используя при этом структуру
struct
FlightSearch
:![](https://habrastorage.org/webt/uc/wj/ny/ucwjny9b6hcww6jqwxveohw01em.png)
Критерий фильтрации включает в себя аэропорт назначения
destination
, аэропорт отправления origin
, авиакомпанию airline и нахождение в воздухе inAir
По значениям этих переменных мы формируем критерий выборки в виде предиката predicate
:![](https://habrastorage.org/webt/vu/zm/33/vuzm33fjxdwnaxmanv-kw7areoe.png)
Безусловно, есть UI для задания параметров выборки
destination
, origin
, airline
иinAir
. Это FilterFlights
(слева), a справа вы видите результат выборки рейсов по заданному критерию:![](https://habrastorage.org/webt/cy/e4/cx/cye4cxf9eacxq415h0ucd5ziocw.png)
В качестве аэропорта назначения Destination мы выбрали аэропорт Chicago O'Hare, a в качестве авиакомпании Airline — United, кроме того, нас интересуют рейсы, находящиеся в воздухе, то есть переключатель Enroute Only установлен в
True
.В результат применения этого фильтра (кнопка
Done
), мы получили 6 рейсов с характеристиками, удовлетворяющими этому критерию.Если мы переключим Enroute Only в состояние
False
, то есть нас будут интересовать не только рейсы, находящиеся в воздухе, но и те, которые уже прилетели или собираются улетать, то мы увидим, что число рейсов увеличится до 8 рейсов, туда войдут два рейса, которые уже прилетели в аэропорт Chicago O'Hare и находятся на земле.И опять слева вы видите UI для задания параметров выборки
destination
, origin
, airline
иinAir
, a справа — результат выборки рейсов по заданному критерию:![](https://habrastorage.org/webt/wh/jz/xn/whjzxnyxldexrxmjiapthdohqxi.png)
Для задания аэропорта назначения Destination используем
Picker
:![](https://habrastorage.org/webt/zv/t1/zv/zvt1zvvsricgugthfkoaj4s8eeq.png)
Нужный нам аэропорт мы выбираем с помощью
Picker
из списка аэропортов, который включает в себя Any
(то есть любой аэропорт). В нашем конкретном случае мы выбираем аэропорт San Francisco:![](https://habrastorage.org/webt/22/w4/nf/22w4nfk09dvisrsiesz_lsysipu.png)
В результате получаем список всех рейсов, направляющихся a аэропорт San Francisco, либо недавно там приземлившихся там:
![](https://habrastorage.org/webt/yj/1f/x-/yj1fx-ompxcu22dnpl9gbuuqclm.png)
Для выбора аэропорта назначения Destination мы можем воспользоваться картой: либо “родной”
SwiftUI Map
, либо интегрированной MapKit
в SwiftUI
:![](https://habrastorage.org/webt/rh/0h/rr/rh0hrrfas8aem-vhhdzexh5t8os.png)
Мы можем начать выбирать пункт назначения Destination прямо на карте, a когда увидим нужный нам аэропорт, то просто щелкнем на его индикаторе:
![](https://habrastorage.org/webt/-3/5c/9r/-35c9ru2ryldyyzgexuwbypqb0o.gif)
Мы выбрали аэропорт Los Angeles Int и получили все рейсы, направляющиеся в этот аэропорт:
![](https://habrastorage.org/webt/fd/tg/zo/fdtgzom1n9o_021y72mqjlxr2y8.png)
Единицы измерения полетной информации.
Каждый отдельный рейс в списке рейсов представлен значительным количеством информации:
![](https://habrastorage.org/webt/n7/ub/ik/n7ubikxxlpcd8kshfomv9j3bgz8.png)
- кодом рейса UAL1780,
- городом вылета Portland,
- городом прилета San Francisco,
- полным названием авиакомпании United Air Lines Inc.,
- типом самолета A319,
- расстоянием от аэропорта отправления до аэропорта назначения distance = 887 km,
- скоростью полета по приборам (ППП) speed = 800 km/h,
- временем взлета Jan 26, 2022, 7:22 PM,
- временем приземления Jan 26, 2022, 8:41 PM,
- длительностью рейса 1 hours 18 min,
- средней скоростью полета aveSpeed = 682 km/h,
- статусом рейса Приземл. / Вырулив.,
- процентом пройденного расстояния 100 %
Нужно отметить, что исходные данные, которые мы получаем от FlightAware о рейсах самолетов, имеют единицы измерения отличные от привычных нам СИ единиц измерения. Например, дальность полета представлена в милях (miles), высота в футах (foots), скорость в узлах (knots). Мы, естественно, хотим отображать на нашем UI значения полетных параметров в СИ единицах измерения: дальность и высоту — в километрах km, скорость — в km/h. Для этого нам не нужно писать дополнительный код, так как в
Swift
уже есть встроенная система единиц измерения длины, скорости и продолжительности соответственно UnitLength
, UnitSpeed
и UnitDuration
, и мы можем легко преобразовывать значения в одних единицах измерения в значения в других единицах измерения, подписывая эти значения соответствующими единицами измерения в определенном формате, которые могут также локализоваться в зависимости от страны. За работу с физическими значениями и их единицами измерения в iOS отвечают объекты
Measurement
— это отдельная тема, о которой можно почитать здесь. Для нашего случая нам понадобятся некоторые дополнительные функции и расширения extension
, которые мы разместили в файле FoundationExtensions.swift:![](https://habrastorage.org/webt/-i/_s/pq/-i_spq9wtsx5ixdy4pvh9ptl0cg.png)
![](https://habrastorage.org/webt/bx/8b/qb/bx8bqblgibrvdfwzrs1pcg8fxms.png)
Это позволит нашей маленькой ViewModel
Flight
преобразовать информацию, поступающую из Model, к виду, необходимому для отображения вView
…![](https://habrastorage.org/webt/if/jo/1p/ifjo1pg38piwzn51fi6smxl8yis.png)
… сформировать на нашем UI строки с расстоянием от аэропорта отправления до аэропорта назначения distance = 887 km и скоростью полета по приборам (ППП) speed = 800 km/h, a также с длительностью рейса 1 hours 18 min и средней скоростью полета aveSpeed = 682 km/h в нужных нам единицах измерения. Причем единицы измерения добавляются к нашим строкам со значениями автоматически. Но этого мало. В зависимости от языка, на который настроен ваш iPhone:
![](https://habrastorage.org/webt/tz/mq/hl/tzmqhlwt46ivpnuoqnite6affa8.png)
… единицы измерения указываются на соответствующем языке. Например, если язык вашего iPhone — русский, то и единицы измерения также будут на русском языке:
![](https://habrastorage.org/webt/tu/46/0m/tu460mvzilkoqwxcbwozmfeq57c.png)
Соответственно, если язык вашего iPhone — английский…
![](https://habrastorage.org/webt/yw/78/ea/yw78eav0qdw3el0qkqhzr5kvwbo.png)
… то и единицы измерения также будут на английском языке:
![](https://habrastorage.org/webt/df/w7/mx/dfw7mxatbav47qijyfayjwwgirc.png)
ЗАКЛЮЧЕНИЕ
Показана комфортная работа
Core Data
и SwiftUI
с реальными взаимосвязанными объектами — полетами Flight
, аэропортами Airport
и авиакомпаниями AirLine
, которые мы получаем на бесплатном сервисе FlightAware и размещаем в Core Data
. Это сильно упрощенная модификация реального приложения Enroute из стэнфордских курсов CS193P 2020, которое а отличие от нашего простого приложения оперативно подкачивает данные с сервера FlightAware.В нашем приложении мы просто демонстрируем работу с взаимосвязями объектов типа one-to-many, а также динамическую настройку фантастической «обертки»
@FetchRequest
в SwiftUI
. Код приложения удается сильно упростить путем использования расширения extension
классов class
объектов Core Data
, сгенерированных Xcode, которые сами по себе уже являются реактивными ViewModel для этих объектов, и в которые удобно спрятать все «шероховатости» взаимодействия Objective-Cориентированного API Core Data
с современным SwiftUI
. Мои эксперименты с полётной информацией показали, что
Core Data
очень эффективно справляется с огромным количеством информации, так что в любом случае «овчинка стоит выделки».Код находится на Github.