Информация об изменениях

Сообщение Верстка UI - обзор подходов от 29.12.2024 10:06

Изменено 29.12.2024 10:52 Shmj

Верстка UI - обзор подходов
Вот что интересное заметил.

Ранее старались разделить — мухи отдельно, котлеты отдельно. Т.е. желали декларативное описание UI и отдельно уже код. Вот примеры

Дедовский MFC .rc (resource script)


Канонически правильный, не мешаем котлеты с мухами:

IDD_MYDIALOG DIALOGEX 0, 0, 200, 150
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "My Dialog"
FONT 8, "MS Sans Serif"
BEGIN
    CONTROL "OK", IDOK, "Button", WS_TABSTOP, 50, 120, 50, 14
    CONTROL "Cancel", IDCANCEL, "Button", WS_TABSTOP, 110, 120, 50, 14
END


Qt Designer UI Language (XML)


  XML
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Пример окна</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLineEdit" name="lineEdit"/>
    </item>
    <item>
     <widget class="QPushButton" name="pushButton">
      <property name="text">
       <string>Нажми меня</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <action name="actionExit">
   <property name="text">
    <string>Выход</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>


QT QML (Qt Modeling Language)


  QML
import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 400
    height: 300
    title: "Пример на QML"

    Column {
        anchors.centerIn: parent
        spacing: 10

        TextField {
            id: inputField
            placeholderText: "Введите текст"
        }

        Button {
            text: "Нажми меня"
            onClicked: console.log("Текст: " + inputField.text)
        }
    }
}


— это уже переходной этап, т.к. кроме декларации — был JavaScript. Но все-же не смешивалось с основным ЯП — как то C++.

WinFormsи Xaml


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

Xaml же уже полностью вернулись к концепции полного разделения — был специальный XML-файл:

  XAML
<Window x:Class="ExampleApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Пример XAML UI" Height="300" Width="400">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <!-- Метка для ввода текста -->
        <TextBlock Grid.Row="0" Grid.Column="0" Margin="10" Text="Введите текст:" VerticalAlignment="Center"/>

        <!-- Текстовое поле для ввода -->
        <TextBox x:Name="InputTextBox" Grid.Row="0" Grid.Column="1" Margin="10" Width="200"/>

        <!-- Кнопка для подтверждения -->
        <Button x:Name="SubmitButton" Grid.Row="1" Grid.ColumnSpan="2" Margin="10" Content="Отправить" Click="SubmitButton_Click"/>

        <!-- Поле для отображения результата -->
        <TextBlock x:Name="ResultTextBlock" Grid.Row="2" Grid.ColumnSpan="2" Margin="10" Text="Здесь будет результат" TextWrapping="Wrap"/>
    </Grid>
</Window>


Что с Web-технологиями


Интересно что ASP.Net WebForms — был смесью HTML и серверных тегов — все-таки позиционировался как декларативный — код на C# размещался отдельно, что поднимало на уровень выше по сравнению с тогдашним PHP:

  ASP.NET Classic WebForms
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ContactForm.aspx.cs" Inherits="MyApp.ContactForm" %>

<!DOCTYPE html>
<html lang="en">
<head runat="server">
    <meta charset="utf-8" />
    <title>Contact Form</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <div class="container">
        <h2>Contact Form</h2>

        <form id="form1" runat="server">
            <div class="form-group">
                <label for="Name">Name</label>
                <asp:TextBox ID="NameTextBox" runat="server" CssClass="form-control" />
            </div>

            <div class="form-group">
                <label for="Email">Email</label>
                <asp:TextBox ID="EmailTextBox" runat="server" CssClass="form-control" />
            </div>

            <div class="form-group">
                <label for="Message">Message</label>
                <asp:TextBox ID="MessageTextBox" runat="server" TextMode="MultiLine" CssClass="form-control" />
            </div>

            <asp:Button ID="SubmitButton" runat="server" Text="Submit" CssClass="btn btn-primary" OnClick="SubmitButton_Click" />
        </form>
    </div>
</body>
</html>


Потом как-то стал популярен MVC везде, в т.ч. в том же PHP он начал активно использоваться, и уже все стало намного проще:

  ASP.NET MVC (Razor)
@model MyApp.Models.ContactForm

@{
    ViewBag.Title = "Contact Form";
}

<h2>@ViewBag.Title</h2>

@using (Html.BeginForm("Submit", "Home", FormMethod.Post))
{
    <div class="form-group">
        @Html.LabelFor(m => m.Name)
        @Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.Email)
        @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.Message)
        @Html.TextAreaFor(m => m.Message, new { @class = "form-control" })
    </div>

    <button type="submit" class="btn btn-primary">Submit</button>
}


  ASP.NET Blazor
@page "/contact"
@using MyApp.Models

<h3>Contact Form</h3>

<EditForm Model="@contactForm" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="form-group">
        <label for="Name">Name</label>
        <InputText id="Name" class="form-control" @bind-Value="contactForm.Name" />
    </div>

    <div class="form-group">
        <label for="Email">Email</label>
        <InputText id="Email" class="form-control" @bind-Value="contactForm.Email" />
    </div>

    <div class="form-group">
        <label for="Message">Message</label>
        <InputTextArea id="Message" class="form-control" @bind-Value="contactForm.Message" />
    </div>

    <button type="submit" class="btn btn-primary">Submit</button>
</EditForm>


Андроид


Используются XML-файлы с описанием элементов формы:

  XML-layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/usernameEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Enter your username" />

    <Button
        android:id="@+id/submitButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Submit" />
</LinearLayout>


iOS


Аналогично, использовался XML для описания:

  Storyboard
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
  <dict>
    <key>UIApplicationSceneManifest</key>
    <dict>
      <key>UIWindowScene</key>
      <dict>
        <key>UIWindowSceneRoleApplication</key>
        <dict>
          <key>UIViewController</key>
          <dict>
            <key>view</key>
            <dict>
              <key>subviews</key>
              <array>
                <dict>
                  <key>UILabel</key>
                  <dict>
                    <key>text</key>
                    <string>Hello, world!</string>
                    <key>frame</key>
                    <string>{{50, 50}, {200, 40}}</string>
                  </dict>
                </dict>
                <dict>
                  <key>UIButton</key>
                  <dict>
                    <key>title</key>
                    <string>Press me</string>
                    <key>frame</key>
                    <string>{{50, 120}, {200, 50}}</string>
                    <key>action</key>
                    <string>buttonPressed:</string>
                  </dict>
                </dict>
              </array>
            </dict>
          </dict>
        </dict>
      </dict>
    </dict>
  </dict>
</plist>


Новые нравы


А теперь смотрите к чему мы опять вернулись.

Flutter


  Flutter
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Форма регистрации')),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: RegistrationForm(),
        ),
      ),
    );
  }
}

class RegistrationForm extends StatefulWidget {
  @override
  _RegistrationFormState createState() => _RegistrationFormState();
}

class _RegistrationFormState extends State<RegistrationForm> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: <Widget>[
          TextFormField(
            controller: _nameController,
            decoration: InputDecoration(labelText: 'Имя'),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Пожалуйста, введите имя';
              }
              return null;
            },
          ),
          TextFormField(
            controller: _emailController,
            decoration: InputDecoration(labelText: 'Электронная почта'),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Пожалуйста, введите email';
              } else if (!RegExp(r'\S+@\S+\.\S+').hasMatch(value)) {
                return 'Введите корректный email';
              }
              return null;
            },
          ),
          TextFormField(
            controller: _passwordController,
            decoration: InputDecoration(labelText: 'Пароль'),
            obscureText: true,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Пожалуйста, введите пароль';
              } else if (value.length < 6) {
                return 'Пароль должен содержать хотя бы 6 символов';
              }
              return null;
            },
          ),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState?.validate() ?? false) {
                // Если форма валидна, отправить данные
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Форма отправлена')),
                );
              }
            },
            child: Text('Отправить'),
          ),
        ],
      ),
    );
  }
}


Kotlin Multiplatform (KMP)


Вариант Compose Multiplatform:

  Compose Multiplatform
@Composable
fun LoginForm() {
    Column {
        TextField(value = username, onValueChange = { username = it }, label = { Text("Username") })
        TextField(value = password, onValueChange = { password = it }, label = { Text("Password") }, visualTransformation = PasswordVisualTransformation())
        Button(onClick = { /* handle login */ }) {
            Text("Login")
        }
    }
}


SwiftUI


И даже новомодный SwiftUI

  SwiftUI
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, world!")
                .font(.title)
                .padding()
            
            Button(action: {
                print("Button pressed")
            }) {
                Text("Press me")
                    .font(.title)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


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

Ранее старались разделить — мухи отдельно, котлеты отдельно. Т.е. желали декларативное описание UI и отдельно уже код. Вот примеры

Дедовский MFC .rc (resource script)

Канонически правильный, не мешаем котлеты с мухами:

IDD_MYDIALOG DIALOGEX 0, 0, 200, 150
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "My Dialog"
FONT 8, "MS Sans Serif"
BEGIN
    CONTROL "OK", IDOK, "Button", WS_TABSTOP, 50, 120, 50, 14
    CONTROL "Cancel", IDCANCEL, "Button", WS_TABSTOP, 110, 120, 50, 14
END


Qt Designer UI Language (XML)

  XML
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Пример окна</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLineEdit" name="lineEdit"/>
    </item>
    <item>
     <widget class="QPushButton" name="pushButton">
      <property name="text">
       <string>Нажми меня</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <action name="actionExit">
   <property name="text">
    <string>Выход</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>


QT QML (Qt Modeling Language)

  QML
import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 400
    height: 300
    title: "Пример на QML"

    Column {
        anchors.centerIn: parent
        spacing: 10

        TextField {
            id: inputField
            placeholderText: "Введите текст"
        }

        Button {
            text: "Нажми меня"
            onClicked: console.log("Текст: " + inputField.text)
        }
    }
}


— это уже переходной этап, т.к. кроме декларации — был JavaScript. Но все-же не смешивалось с основным ЯП — как то C++.

WinForms и Xaml

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

Xaml же уже полностью вернулись к концепции полного разделения — был специальный XML-файл:

  XAML
<Window x:Class="ExampleApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Пример XAML UI" Height="300" Width="400">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <!-- Метка для ввода текста -->
        <TextBlock Grid.Row="0" Grid.Column="0" Margin="10" Text="Введите текст:" VerticalAlignment="Center"/>

        <!-- Текстовое поле для ввода -->
        <TextBox x:Name="InputTextBox" Grid.Row="0" Grid.Column="1" Margin="10" Width="200"/>

        <!-- Кнопка для подтверждения -->
        <Button x:Name="SubmitButton" Grid.Row="1" Grid.ColumnSpan="2" Margin="10" Content="Отправить" Click="SubmitButton_Click"/>

        <!-- Поле для отображения результата -->
        <TextBlock x:Name="ResultTextBlock" Grid.Row="2" Grid.ColumnSpan="2" Margin="10" Text="Здесь будет результат" TextWrapping="Wrap"/>
    </Grid>
</Window>


Что с Web-технологиями

Интересно что ASP.Net WebForms — был смесью HTML и серверных тегов — все-таки позиционировался как декларативный — код на C# размещался отдельно, что поднимало на уровень выше по сравнению с тогдашним PHP:

  ASP.NET Classic WebForms
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ContactForm.aspx.cs" Inherits="MyApp.ContactForm" %>

<!DOCTYPE html>
<html lang="en">
<head runat="server">
    <meta charset="utf-8" />
    <title>Contact Form</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <div class="container">
        <h2>Contact Form</h2>

        <form id="form1" runat="server">
            <div class="form-group">
                <label for="Name">Name</label>
                <asp:TextBox ID="NameTextBox" runat="server" CssClass="form-control" />
            </div>

            <div class="form-group">
                <label for="Email">Email</label>
                <asp:TextBox ID="EmailTextBox" runat="server" CssClass="form-control" />
            </div>

            <div class="form-group">
                <label for="Message">Message</label>
                <asp:TextBox ID="MessageTextBox" runat="server" TextMode="MultiLine" CssClass="form-control" />
            </div>

            <asp:Button ID="SubmitButton" runat="server" Text="Submit" CssClass="btn btn-primary" OnClick="SubmitButton_Click" />
        </form>
    </div>
</body>
</html>


Потом как-то стал популярен MVC везде, в т.ч. в том же PHP он начал активно использоваться, и уже все стало намного проще:

  ASP.NET MVC (Razor)
@model MyApp.Models.ContactForm

@{
    ViewBag.Title = "Contact Form";
}

<h2>@ViewBag.Title</h2>

@using (Html.BeginForm("Submit", "Home", FormMethod.Post))
{
    <div class="form-group">
        @Html.LabelFor(m => m.Name)
        @Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.Email)
        @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.Message)
        @Html.TextAreaFor(m => m.Message, new { @class = "form-control" })
    </div>

    <button type="submit" class="btn btn-primary">Submit</button>
}


  ASP.NET Blazor
@page "/contact"
@using MyApp.Models

<h3>Contact Form</h3>

<EditForm Model="@contactForm" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="form-group">
        <label for="Name">Name</label>
        <InputText id="Name" class="form-control" @bind-Value="contactForm.Name" />
    </div>

    <div class="form-group">
        <label for="Email">Email</label>
        <InputText id="Email" class="form-control" @bind-Value="contactForm.Email" />
    </div>

    <div class="form-group">
        <label for="Message">Message</label>
        <InputTextArea id="Message" class="form-control" @bind-Value="contactForm.Message" />
    </div>

    <button type="submit" class="btn btn-primary">Submit</button>
</EditForm>


Андроид

Используются XML-файлы с описанием элементов формы:

  XML-layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/usernameEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Enter your username" />

    <Button
        android:id="@+id/submitButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Submit" />
</LinearLayout>


iOS

Аналогично, использовался XML для описания:

  Storyboard
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
  <dict>
    <key>UIApplicationSceneManifest</key>
    <dict>
      <key>UIWindowScene</key>
      <dict>
        <key>UIWindowSceneRoleApplication</key>
        <dict>
          <key>UIViewController</key>
          <dict>
            <key>view</key>
            <dict>
              <key>subviews</key>
              <array>
                <dict>
                  <key>UILabel</key>
                  <dict>
                    <key>text</key>
                    <string>Hello, world!</string>
                    <key>frame</key>
                    <string>{{50, 50}, {200, 40}}</string>
                  </dict>
                </dict>
                <dict>
                  <key>UIButton</key>
                  <dict>
                    <key>title</key>
                    <string>Press me</string>
                    <key>frame</key>
                    <string>{{50, 120}, {200, 50}}</string>
                    <key>action</key>
                    <string>buttonPressed:</string>
                  </dict>
                </dict>
              </array>
            </dict>
          </dict>
        </dict>
      </dict>
    </dict>
  </dict>
</plist>


Новые нравы


А теперь смотрите к чему мы опять вернулись.

Flutter

  Flutter
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Форма регистрации')),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: RegistrationForm(),
        ),
      ),
    );
  }
}

class RegistrationForm extends StatefulWidget {
  @override
  _RegistrationFormState createState() => _RegistrationFormState();
}

class _RegistrationFormState extends State<RegistrationForm> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: <Widget>[
          TextFormField(
            controller: _nameController,
            decoration: InputDecoration(labelText: 'Имя'),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Пожалуйста, введите имя';
              }
              return null;
            },
          ),
          TextFormField(
            controller: _emailController,
            decoration: InputDecoration(labelText: 'Электронная почта'),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Пожалуйста, введите email';
              } else if (!RegExp(r'\S+@\S+\.\S+').hasMatch(value)) {
                return 'Введите корректный email';
              }
              return null;
            },
          ),
          TextFormField(
            controller: _passwordController,
            decoration: InputDecoration(labelText: 'Пароль'),
            obscureText: true,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Пожалуйста, введите пароль';
              } else if (value.length < 6) {
                return 'Пароль должен содержать хотя бы 6 символов';
              }
              return null;
            },
          ),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState?.validate() ?? false) {
                // Если форма валидна, отправить данные
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Форма отправлена')),
                );
              }
            },
            child: Text('Отправить'),
          ),
        ],
      ),
    );
  }
}


Kotlin Multiplatform (KMP)

Вариант Compose Multiplatform:

  Compose Multiplatform
@Composable
fun LoginForm() {
    Column {
        TextField(value = username, onValueChange = { username = it }, label = { Text("Username") })
        TextField(value = password, onValueChange = { password = it }, label = { Text("Password") }, visualTransformation = PasswordVisualTransformation())
        Button(onClick = { /* handle login */ }) {
            Text("Login")
        }
    }
}


SwiftUI

И даже новомодный SwiftUI

  SwiftUI
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, world!")
                .font(.title)
                .padding()
            
            Button(action: {
                print("Button pressed")
            }) {
                Text("Press me")
                    .font(.title)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


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