Code
// ViewController.swift
// Test_osx
//
// Created by Evgeny Veresov on 26.09.2018.
// Copyright © 2018 Evgeny Veresov. All rights reserved.
//
import Cocoa
import Alamofire
// Перевод Date and Time в String
extension Date {
var formatter: String{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let dateString = dateFormatter.string(from:Date())
return dateString
}
}
// Имена таблиц
enum Table :String {
case job = "jobTable"
case employe = "employeTable"
}
// Расширение для включения кнопки редактирования
extension ViewController:NSTextFieldDelegate {
func controlTextDidEndEditing(_ obj: Notification) {
if currentTable == Table.employe.rawValue {
updateRecordEmployeOutlet.isEnabled = true
return
}
if currentTable == Table.job.rawValue {
updateRecordJobOutlet.isEnabled = true
return
}
print("Ошибка при выборе таблицы обновления")
}
}
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
// Массив структур Employe
@objc dynamic var myEmployeArray: [myEmploye] = []
// Массив структур Job
@objc dynamic var myJobArray : [myJob] = []
//Сылка на таблицу Job
@IBOutlet var tableViewJob: NSTableView!
// Ссылка на таблицу Employe
@IBOutlet var tableViewEmploye: NSTableView!
// Текущая таблица
var currentTable:String = ""
// Выбранная строка в таблице Job
var selectRowJob:Int = -1
// Выбранная строка в таблице Employe
var selectRowEmploye:Int = -1
// Признак первого запуска приложения
var flagLoadEndRefresh = true
// Add Employe
@IBOutlet var addRecordEmployeOutlet: NSButton!
// Кнопка Add Employe
@IBAction func addRecordEmploye(_ sender: Any) {
addEmployeTable(firstName: firstName.stringValue,
secondName: secondName.stringValue,
lastName: lastName.stringValue,
phone: phone.stringValue,
photo: image)
}
// Remove Employe
@IBOutlet var removeRecordEmployeOutlet: NSButton!
@IBAction func removeRecordEmploye(_ sender: Any) {
// Если выбрана таблица Employe
if currentTable == Table.employe.rawValue && selectRowEmploye != -1 {
// Удалим запись
deleteEmploye(numberRow: myEmployeArray[selectRowEmploye].employeId)
tableViewJob.deselectRow(selectRowJob)
tableViewEmploye.deselectRow(selectRowEmploye)
//deleteRecOut.isEnabled = false
removeRecordEmployeOutlet.isEnabled = false
// Обнулим признаки
emptyFlag()
self.getEmployeAll()
self.getJobAll()
}
}
// Update Employe
@IBOutlet var updateRecordEmployeOutlet: NSButton!
@IBAction func updateRecordEmploye(_ sender: NSButton) {
if currentTable == Table.employe.rawValue {
updateEmploye(employeId: myEmployeArray[selectRowEmploye].employeId,
firstName: myEmployeArray[selectRowEmploye].firstName,
secondName: myEmployeArray[selectRowEmploye].secondName,
lastName: myEmployeArray[selectRowEmploye].lastName,
phone: myEmployeArray[selectRowEmploye].phone,
photo: image,
jobs: nil)
tableViewJob.deselectRow(selectRowJob)
tableViewEmploye.deselectRow(selectRowEmploye)
updateRecordEmployeOutlet.isEnabled = false
removeRecordEmployeOutlet.isEnabled = false
}
}
// Find in Employe
@IBOutlet var findRecordEmployeOutlet: NSButton!
@IBAction func findRecordEmploye(_ sender: Any) {
if firstName.stringValue != ""
&& secondName.stringValue != ""
&& lastName.stringValue != "" {
// Очистим массив
myEmployeArray.removeAll()
// Сформируем параметры и вызовем функцию
findEmployeParameter(param: "firstName="
+ firstName.stringValue
+ "&"
+ "secondName="
+ secondName.stringValue
+ "&"
+ "lastName="
+ lastName.stringValue)
// Перезагрузим таблицу
tableViewEmploye.reloadData()
}
else {
print("Не заполнены поля для поиска ..")
}
}
// Add Record Job
@IBOutlet var addRecordJobOutlet: NSButton!
@IBAction func addRecordJob(_ sender: NSButton) {
addJobTable(
titleJob: titleJobField.stringValue,
dueDate: dueDataField.stringValue,
isComplete: (isCompleteField != nil),
assignedTo: assignedToField!.integerValue)
}
// Remove Job
@IBOutlet var removeRecordJobOutlet: NSButton!
@IBAction func removeRecordJob(_ sender: NSButton) {
if currentTable == Table.job.rawValue && selectRowJob != -1 {
// Удалим запись
deleteJob(numberRow: myJobArray[selectRowJob].jobId)
tableViewJob.deselectRow(selectRowJob)
tableViewEmploye.deselectRow(selectRowEmploye)
removeRecordJobOutlet.isEnabled = false
// Обнулим признаки
emptyFlag()
self.getEmployeAll()
self.getJobAll()
}
}
// Update Job
@IBOutlet var updateRecordJobOutlet: NSButton!
@IBAction func updateRecordJob(_ sender: NSButton) {
if currentTable == Table.job.rawValue
{
updateJob(titleJob: myJobArray[selectRowJob].titleJob,
jobId: myJobArray[selectRowJob].jobId,
dueDate: myJobArray[selectRowJob].dueDate,
completedStatus: Bool(myJobArray[selectRowJob].isComplete)!,
assignedTo: myJobArray[selectRowJob].assignedTo)
tableViewJob.deselectRow(selectRowJob)
tableViewEmploye.deselectRow(selectRowEmploye)
updateRecordJobOutlet.isEnabled = false
removeRecordJobOutlet.isEnabled = false
}
}
// Поля NSTextField на форме для таблицы Job
@IBOutlet var titleJobField: NSTextField!
@IBOutlet var dueDataField: NSTextField!
@IBOutlet var isCompleteField: NSTextField!
@IBOutlet var assignedToField: NSTextField!
// Поля NSTextField на форме для таблицы Employe
@IBOutlet var firstName: NSTextField!
@IBOutlet var secondName: NSTextField!
@IBOutlet var lastName: NSTextField!
@IBOutlet var phone: NSTextField!
@IBOutlet var photo: NSTextField!
// Фотография сотрудника
@IBOutlet var imageEmploye: NSImageView!
//End Load
override func viewDidLoad() {
super.viewDidLoad()
// Значение для Iscomplete
isCompleteField.stringValue = "true"
// Значение для date
dueDataField.stringValue = Date().formatter
// Выключим кнопки
removeRecordEmployeOutlet.isEnabled = false
updateRecordEmployeOutlet.isEnabled = false
removeRecordJobOutlet.isEnabled = false
updateRecordJobOutlet.isEnabled = false
// Установим размер шрифта в заголовках таблиц
tableViewJob.tableColumns.forEach { (column) in
column.headerCell.attributedStringValue = NSAttributedString(string: column.title, attributes: [NSAttributedString.Key.font: NSFont.titleBarFont(ofSize: 13.5)])
}
tableViewEmploye.tableColumns.forEach { (column) in
column.headerCell.attributedStringValue = NSAttributedString(string: column.title, attributes: [NSAttributedString.Key.font: NSFont.titleBarFont(ofSize: 13.5)])
}
// Считаем все записи таблицы Employe
getEmployeAll()
// Считаем все записи таблицы Job
getJobAll()
}
// Кнопка сброса
@IBAction func refreshAll(_ sender: Any) {
removeRecordEmployeOutlet.isEnabled = false
updateRecordEmployeOutlet.isEnabled = false
removeRecordJobOutlet.isEnabled = false
updateRecordJobOutlet.isEnabled = false
// Установим фокус на FirstName
self.view.window?.makeFirstResponder(self.firstName)
// Обновим все
clearField()
self.getEmployeAll()
self.getJobAll()
}
// Add record Job при нажатии Enter
@IBAction func enterAssignTo(_ sender: NSTextField) {
addJobTable(
titleJob: titleJobField.stringValue,
dueDate: dueDataField.stringValue,
isComplete: (isCompleteField != nil),
assignedTo: assignedToField!.integerValue)
self.view.window?.makeFirstResponder(self.titleJobField)
}
// ******************* FUNCTIONS **********************
// Click пользователя на строке таблицы Job, Employe
func tableViewSelectionDidChange(_ notification: Notification) {
let selectTable = notification.object as! NSTableView
let identifier = selectTable.identifier
// Job
switch identifier?.rawValue {
case Table.job.rawValue:
if tableViewJob.selectedRow != -1 {
selectRowJob = tableViewJob.selectedRow
tableViewEmploye.deselectRow(selectRowEmploye)
currentTable = (identifier?.rawValue)!
removeRecordJobOutlet.isEnabled = true
} else {
currentTable = ""
}
//Employe
case Table.employe.rawValue:
if tableViewEmploye.selectedRow != -1 {
selectRowEmploye = tableViewEmploye.selectedRow
tableViewJob.deselectRow(selectRowJob)
currentTable = (identifier?.rawValue)!
let charArray = myEmployeArray[selectRowEmploye].photo
let data = NSData(base64Encoded: charArray, options: NSData.Base64DecodingOptions(rawValue: 0))
imageEmploye.imageScaling = .scaleAxesIndependently
imageEmploye.image = NSImage(data: data! as Data)
removeRecordEmployeOutlet.isEnabled = true
} else {
currentTable = ""
}
default:
currentTable = ""
print("Ошибка при выборе таблицы!")
break
}
}
// Get запрос всех записей в таблице Employee
func getEmployeAll() -> Void {
Employe.employeByAll(all: "all") { result in
// Ошибка при получении данных
if let error = result.error {
print("Ошибка GET запроса emplloye/all")
print(error)
return
}
// Если значение равно nil
guard let employeArray = result.value else {
print("GET запрос employe/all, result.value = nil")
return
}
/*
// Если все хорошо - распечатаем
for item in employeArray {
print(item.description())
}
*/
// Выведем в ArrayController
self.copyArrayControllerEmploye(employe: employeArray)
// Перезагрузим Employe
self.tableViewEmploye.reloadData()
// Выделим первую запись в таблице Employe
if self.flagLoadEndRefresh == true {
self.view.window?.makeFirstResponder(self.tableViewEmploye)
self.tableViewEmploye.selectRowIndexes(NSIndexSet(index: 0) as IndexSet, byExtendingSelection: false)
self.flagLoadEndRefresh = false
}
}
}
// Get запрос всех записей таблицы Job
func getJobAll() -> Void {
Job.jobByAll(all: "all") { result in
// Ошибка при получении данных
if let error = result.error {
print("Ошибка GET запроса job/all")
print(error)
return
}
// Если значение равно nil
guard let jobArray = result.value else {
print("GET запрос job/all, result.value = nil")
return
}
/*
// Если все хорошо - распечатаем
for item in jobArray {
print(item.description())
}
*/
// Выведем в таблицу Job
self.copyArrayControllerJob(job: jobArray)
self.tableViewJob.reloadData()
}
}
// Добавление записи в таблицу Job
func addJobTable (titleJob: String, dueDate:String, isComplete:Bool, assignedTo:Int) {
// Создадим новую структуру Job
guard let newJob = Job(titleJob: titleJob,
jobId: nil,
dueDate: dueDate,//T11:40:17.713187",
completedStatus: isComplete,
assignedTo: assignedTo)
else{
print("Error create newJob")
return
}
newJob.save(linkEmploye:assignedToField.integerValue) { result in
guard result.error == nil else {
print("Error calling POST on Job")
print(result.error!)
return
}
guard let jobIdValue = result.value else {
print("Error calling POST on Job. result is nil")
return
}
// Выведем все ID Job привязанные к первой записи в Employe
print(jobIdValue)
self.getEmployeAll()
self.getJobAll()
}
}
// Добавление новой записи в таблицу Employee
func addEmployeTable (firstName:String, secondName:String, lastName:String, phone:String, photo:String){
// Создадим новую структуру Employe
guard let newEmploye = Employe(employeId: nil, firstName: firstName, secondName: secondName, lastName: lastName, phone: phone, photo: image, jobs: nil )
else {
print("Error create newEmploye")
return
}
newEmploye.save() { result in
guard result.error == nil else {
print("Error calling POST on Job")
print(result.error!)
return
}
guard let jobIdValue = result.value else {
print("Error calling POST on Job. result is nil")
return
}
// Все хорошо!
self.getEmployeAll()
print(jobIdValue)
}
}
// Поиск в таблице Employee по параметрам
func findEmployeParameter (param: String){
Employe.employeByParam(parameter: param) { result in
// Ошибка при получении данных
if let _ = result.error {
print("Ошибка GET запроса emplloye/parameter")
return
}
// Если значение равно nil
guard let employeArray = result.value else {
print("GET запрос employe/parameter, result.value = nil")
return
}
/*
// Если все хорошо - распечатаем
for item in employeArray {
print(item.description())
}
*/
// Выведем в таблицу Employe
self.copyArrayControllerEmploye(employe: employeArray)
self.tableViewEmploye.reloadData()
}
}
// Заполнение ArrayController для Employe
func copyArrayControllerEmploye (employe:[Employe]){
self.myEmployeArray.removeAll()
if employe.count > 0 {
for i in 0 ... employe.count - 1 {
self.myEmployeArray.append(myEmploye())
self.myEmployeArray[i].employeId = employe[i].employeId!
self.myEmployeArray[i].firstName = employe[i].firstName
self.myEmployeArray[i].secondName = employe[i].secondName
self.myEmployeArray[i].lastName = employe[i].lastName
self.myEmployeArray[i].phone = employe[i].phone
self.myEmployeArray[i].photo = employe[i].photo
let jobs = employe[i].jobs
var strJobsId = ""
for job in jobs! {
strJobsId = strJobsId + String(job.jobId!) + ";"
}
self.myEmployeArray[i].jobs = strJobsId
}
}
}
// Заполнение ArrayController для Job
func copyArrayControllerJob (job:[Job]){
self.myJobArray.removeAll()
if job.count > 0 {
for i in 0...job.count - 1 {
self.myJobArray.append(myJob())
self.myJobArray[i].titleJob = job[i].titleJob
self.myJobArray[i].jobId = job[i].jobId!
self.myJobArray[i].dueDate = job[i].dueDate
self.myJobArray[i].isComplete = String(job[i].isComplete)
self.myJobArray[i].assignedTo = job[i].assignedTo!
}
}
}
// Обновление записи в таблице Employe
func updateEmploye (employeId: Int?, firstName: String, secondName: String, lastName: String, phone: String, photo: String, jobs: [Job]?) {
// Создадим новую структуру Employe
guard let newEmploye = Employe(employeId: nil, firstName: firstName, secondName: secondName, lastName: lastName, phone: phone, photo: image, jobs: nil)
else {
print("Ошибка при создании Employe в PUT запросе")
return
}
newEmploye.update(employeId: employeId!) { result in
guard result.error == nil else {
print("Ошибка при запросе обновлении Employe")
print(result.error!)
return
}
guard let updateId = result.value else {
print("Ошибка Id в Employe")
return
}
// Все хорошо!
print(updateId)
self.getEmployeAll()
self.getJobAll()
}
}
// Обновление записи в таблице Job
func updateJob (titleJob: String, jobId : Int, dueDate:String, completedStatus:Bool, assignedTo:Int) {
// Создадим новую структуру Job
guard let newJob = Job(titleJob: titleJob, jobId: nil, dueDate: dueDate, completedStatus: completedStatus, assignedTo : assignedTo)
else {
print("Ошибка при создании Job в PUT запросе")
return
}
newJob.update(jobId: jobId) { result in
guard result.error == nil else {
print("Ошибка при запросе обновлении Job")
print(result.error!)
return
}
guard let updateId = result.value else {
print("Ошибка Id в Job")
return
}
// Все хорошо!
self.getEmployeAll()
self.getJobAll()
print(updateId)
}
}
// Удаление записи в таблице Job
func deleteJob(numberRow:Int){
Alamofire.request(TodoRouter.deleteJob(numberRow))
.responseJSON { response in
guard response.result.error == nil else {
print("Ошибка при удалении записи Job..")
print(response.result.error!)
return
}
print("Delete Row \(numberRow) in Job OK!")
// Перезагрузим таблицу job
self.getJobAll()
}
}
// Удаление записи в таблице Employe
func deleteEmploye(numberRow:Int) -> Void {
Alamofire.request(TodoRouter.deleteEmploye(numberRow))
.responseJSON { response in
guard response.result.error == nil else {
print("Ошибка при удалении записи Employe")
print(response.result.error!)
return
}
print("Delete row \(numberRow) in Employe OK!")
self.imageEmploye.image = nil
// Перезагрузим таблицы
self.getEmployeAll()
self.getJobAll()
}
}
// Обнуления признаков при удалении Row
func emptyFlag() {
currentTable = ""
selectRowJob = -1
selectRowEmploye = -1
}
// Очистка полей ввода
func clearField() {
firstName.stringValue = ""
secondName.stringValue = ""
lastName.stringValue = ""
phone.stringValue = ""
titleJobField.stringValue = ""
assignedToField.stringValue = ""
}
}
REST API macOS. Client Application.
В статье представлен вариант клиента для сервера приложения.
В клиенте реализованы REST API и пользовательский интерфейс для работы с базой данных на сервере.
Программа выполняет все операции CRUD.
Клиент написан в macOS 10.14.3, средствами XСode 10.1 и языка Swift 4.2.
В проекте использована библиотека Alamofire
и методика построения приложений предложенная
Christina Moulton в ее книге iOS Apps with REST APIs
Пользовательский интерфейс и реализация REST API полностью разделены и находятся в разных классах и структурах.
Вызовы функций REST инициируются из интерфейса пользователя. Формат запросов к серверу определяется их реализацией на сервере.
В прграмме использованы компоненты NSArrayController, NSTableView, NSImageView, NSTextField и другие.
Пример клиента можно свободно загрузить
1. Интерфейс пользователя.
Вот так выглядит программа после старта.
В состав пользовательского интерфейса входят следующие элементы.
Таблицы с именами: Employe и Job - это объекты класса NSTableView.
Каждая из таблиц подключена к соответствующему ей NSArrayController.
Данные из NSArrayController отображаются в NSTableView.
В объекты классов NSArrayController информация записывается в результате выполнения запросов к Web серверу.
Таким образом, можно сказать, что информация в таблицах Employe и Job клиента - это текущая копия таблиц базы данных сервера.
Поля типа NSTextField, что находятся справа от таблиц предназначены для ввода данных оператором.
Затем эти данные передаются в качестве параметров для запросов к серверу на добавления записей в базу данных.
Кнопки в составе таблиц с обозначением: «+», «-», … следует
читать как: «Добавить», «Удалить», «Обновить», «Найти».
При нажатии на любую из этих кнопок будет активирован соответствующий запрос к серверу приложения.
Элемент NSImageView, что находиться в верхней части приложения, предназначен для отображения
фотографии выбранного в таблице Employe сотрудника.
Кнопка «Refresh All» запускает процесс считывания всех данных из таблиц Employe и Job на SQL сервере.
Данные из этих таблиц передаются клиенту и отображаются в одноименных таблицах приложения.
2. Организация запросов к серверу.
Для дальнейшего чтения Вам понадобятся исходные тексты файлов WebServerController.cs и ViewController.swift.
По загрузке приложения, при выполнении функции viewDidLoad(), что находится в ViewController.swift, будут
вызваны функции getEmployeAll() и getJobAll() которы сформируют GET запросы к серверу:
https://localhost:5001/employe/all и https://localhost:5001/job/all.
По этим запросам из таблиц на SQL сервере будут считаны все данные и переданы в клиент для отображения в таблицах Employe и Job.
Запросы выполняются аналогично тем, что вызываются при нажатии на кнопку «Refresh All»
Каждая таблица NSTableView имеет кнопки для добавления, удаления, обновления и поиска данных.
Нажатие на любую из этих кнопок приводит к формированию соответствующего запроса для сервера.
Например, при нажатии на кнопку «+» выполнятся POST запрос: https://localhost:5001/employe или https://localhost:5001/job/int в зависимости от таблицы,
в которой нажата кнопка. Это запрос на добавление записи. По этому запросу в соответствующую таблицу на сервере будет добавлена новая запись.
Данные для новой записи будут взяты из полей, что справа от каждой из таблиц.
При добавлении записи в таблицу Job будет выполнена ее привязка к записи в таблице Employe.
После операции добавления записи автоматически запускается операция на считывание всех данных из таблиц Employe и Job на SQL сервере,
передача их в приложение клиент и отображение данных в таблицах NSTableView.
Так двойные запросы здесь и далее синхронизируются по очередности.
При нажатии на кнопку «-» выполнятся DELETE запрос: https://localhost:5001/employe/int или https://localhost:5001/job/int в
зависимости от таблицы в которой нажата кнопка. Это запрос на удаление записи.
По этому запросу из соответствующей таблицы на сервере будет удалена указанная в запросе запись.
Удаляемая запись должна быть выделена в таблице клиента.
Далее запускается операция считывания данных из таблиц Employe и Job и передача их клиенту для отображения.
При нажатии на кнопку «Update» выполнятся PUT запрос: https://localhost:5001/employe/int или https://localhost:5001/job/int в зависимости от таблицы, в которой нажата кнопка.
Это запрос на обновление записи. По этому запросу в соответствующей таблице на сервере будет обновлена указанная в запросе запись.
Обновляемая запись должна быть выделена в таблице клиента и изменена.
Далее запускается операция считывания данных из таблиц Employe и Job и передача их клиенту для отображения.
При нажатии на кнопку «Find» в таблице Employe выполнятся GET запрос: https://localhost:5001/?firstname=xxxx&secondname=xxxx&lastname=xxxx.
Это запрос на поиск записи по указанным параметрам.
Параметры должны быть введены в поля, что справа от таблицы.
Указанная запись будет найдена в базе данных и передана в клиент для отображения.
Все ли разработанные на сервере запросы реализованы в клиенте? Нет.
А что-же не реализовано?
Например, одновременное добавление записей в обе таблицы или привязка записей в таблице Job к записям в Employe.
При необходимости читатель может дописать их сам или обратиться ко мне.
В заключении, обратите внимание на изменения типа данных поля Photo при передаче с сервера в приложение клиент.
Изначально, в C# это тип определен как byte[], затем Web сервер переводит это поле в тип base64, а в ViewController.swift
он преобразуется для отображения в NSImageView в тип Data.
Ну, а в базе данных SQL, что ожидаемо, это совершенно другой тип. :).
3. Исходный текст файла ViewController.swift.
Если есть вопросы - спрашивайте не стесняйтесь.
Всего доброго, Евгений Вересов.
10.03.2019 года.
|