Создание проекта

Поехали!

Запускаем Xcode и создаем новый проект

Во вкладке iOS выбираем Single View App и нажимаем Next

даем название

создаем папку Developer (если не создана)

Если в домашней папке создать папку с именем Developer, то система присвоит ей специальную иконку.

Нажимаем на кнопку Resume или клавиатурный эквивалент ⌥ ⌘ P

Клавиатурные эквиваленты Xcode

Minimap я отключил, чтоб места не занимало

Ну вот и все! Мы создали программу, она у нас выводит строку «Hello, World!» на экран, это можно видеть в Превью, а также ее можно уже запустить на симуляторе, на iPhone и на других устройствах.

Идем дальше. Мы хотим, чтоб наша программа дала нам возможность расставлять шарики на поле, удаляла шарики, когда составляются линии из пяти и более шариков, выкидывала произвольно дополнительные шарики. Оригинальный интерфейс представлен на картинке. Для начала мы реализуем верхнюю правую надпись, там выводятся очки игрока

Текст «Hello, World!» заменим на «30»

Удерживая клавишу Command нажмем на «30» в превью, появится меню, выберем Show SwiftUI Inspector…

Выберем Font: Title (по умолчанию Inherited)

для этого нажимаем на текущем значении и выбираем нужное из меню

так же изменим начертание и цвет шрифта, как указано на картинке:

В превью мы видим как меняется наш экран, а слева это сразу же отражается в коде, и наоборот, добавляя что то или удаляя из кода, тут же изменяется экран на превью.

struct ContentView: View {
  var body: some View {
    Text("30")
      .font(.title)
      .fontWeight(.bold)
      .foregroundColor(Color.green)
  }
}

Чтоб каждый раз не вызывать окно Инспектора, можно открыть боковую панель.

Сейчас наш текст расположен в середине экрана, а нам нужно чтоб он был справа. Этого можно достичь с помощью горизонтального стека. Удерживая клавишу Command щелкаем на слове Text и выберем Embed in HStack из меню

Пока ничего на экране не изменилось, но у нас появилась возможность добавить в стек Spacer(). Это можно сделать вызвав Библиотеку через кнопку «+» или клавиатурный эквивалент ⇧⌘L

вот что у нас получилось

Теперь нам необходимо сдвинуть надпись вверх. Для этого мы так же воспользуемся Spacer. Выберем его из библиотеки и бросим на превью как показано на картинке, так чтоб получился новый вертикальный стек

Вот что получилось

для того чтоб добавить отступ по краям, к вертикальному стеку добавим padding()

Вот что получилось

В игре нам потребуется выводить набранные игроком очки, для этого мы заведем переменную score с начальным значением 0. А выводить ее будем через «\(score)»

Пришло время добавить кнопку. Ее можно так же выбрать из библиотеки.

Дадим название кнопке «Restart», а в действие добавим увеличение переменной score на 10, слово self необходимо потому что мы находимся в замыкании. «Reference to property ‘score’ in closure requires explicit ‘self.’ to make capture semantics explicit»

Button(action: {
  self.score += 10
}) {
  Text("Restart")
}

Нам не дадут это сделать: «Left side of mutating operator isn’t mutable: ‘self’ is immutable». Добавим @State в обявлении переменной

@State var score = 0

Теперь проверим, как работает кнопка. Теперь мы можем сделать это в превью, не запуская симулятор. Необходимо нажать на кнопку «Live preview»

Кнопку то же сдвинем в право. Для этого добавим горизонтальный стек и Spacer:

HStack {
  Spacer()
  Button(action: {
    self.score += 10
  }) {
    Text("Restart")
  }
}

Запустим нашу программу в симуляторе. Убедимся что в вертикальном и горизонтальном режиме наши надписи находятся в своих углах (а не где нибудь за пределами экрана)

 Мы можем выделить часть когда в отельное View, в нашем случае мы хотели бы наилучший результат выводился в левом верхнем углу тем же стилем, что и в правом, для этого необходимо с нажатой клавишей Command нажать на Text и выбрать Extract Subview

По умолчанию будет дано имя ExtractedView, которое можно тут же помять на свое ScoreView

В нашем случае будет ошибка Use of unresolved identifier ‘score’, эту ошибку мы исправим объявив в новом View переменную var score: Int

struct ScoreView: View {
  var score: Int
  var body: some View {
    Text("\(score)")
      .font(.title)
      .fontWeight(.bold)
      .foregroundColor(Color.green)
  }
}

Теперь, при обращение к этому View, мы можем задавать значение score

      HStack {
        ScoreView(score: 100)
        Spacer()
        ScoreView(score: score)
      }

Вот такая красота у нас получилась

Добавим еще одну кнопку Settings, которой будем вызывать окно для изменения настроек (количество цветов, включать показ следующих шариков и другие)

      HStack {
        Button(action: {}) {
        Text("Settings")
        }
        Spacer()
        Button(action: {
          self.score += 10
        }) {
          Text("Restart")
        }
      }

так выглядят наши кнопки

Добавим ZStack, он позволит нам располагать объекты слоями, друг над другом. Этого стека в меню нет, поэтому я выбираю вставить в HStack, а потом меняю название на ZStack. Выделим в отдельное View все что касается вывода результатов и кнопок. Туда же перенесем объявление переменной score, либо уберем ее вообще. В дальнейшем она у нас будет объявлена в другом месте. А кнопке Restart будет поручена другая задача:)

struct ControlPanelView: View {
  @State var score = 0
  var body: some View {
    VStack {
      HStack {
        ScoreView(score: 100)
        Spacer()
        ScoreView(score: score)
      }
      Spacer()
      HStack {
        Button(action: {}) {
          Text("Settings")
        }
        Spacer()
        Button(action: {
          self.score += 10
        }) {
          Text("Restart")
        }
      }
    }
    .padding()
  }
}

Вот что получилось

struct ContentView: View {
  var body: some View {
    ZStack {
      ControlPanelView()
    }
  }
}

Вставим картинку игрового поля gamefield.pdf Просто переносим ее в папку Assets.xcassets нашего проекта

Картинка нарисована в Illustrator и сохранена в pdf, это позволяет менять ее размеры не теряя качества изображения. Я отметил галочками Resizing и выбрал Single Scale

Чтоб не ошибиться с названием файла картинки, можно выбрать картину из библиотеки, в коде появится Image(«gamefield»)

Картинка может не поместиться на экране, для этого мы добавим модификаторы resizable и aspectRatio

    ZStack {
      ControlPanelView()
      Image("gamefield")
        .resizable()
        .aspectRatio(contentMode: .fit)
    }

Вот такая красота получилась

Черный фон получим за счет Color.black, а для того чтоб было заполнено все пространство экрана применим edgesIgnoringSafeArea

struct ContentView: View {
  var body: some View {
    ZStack {
      Color.black
        .edgesIgnoringSafeArea(.all)
      ControlPanelView()
      Image("gamefield")
        .resizable()
        .aspectRatio(contentMode: .fit)
    }
  }
}

Игра готова! Можно размещать в App Store! Хотя… надо еще шариков добавить

Если у вас возникли вопросы или вы хотите принять участие в написании приложения буду получить от вас письмо. Скачать проект iLines01.zip


SwiftUI Tutorials