Понимание ScrollView и создание UI карусели

ссылка на 3ю часть

В этом уроке мы разберемся с таким элементом как ScrollView для создания длинных прокручивающихся полотен. Часто бывает такое что наш контент слишком большой но при этом статичный, ввиду этого использование таблиц или коллекций UITableView или UICollectionView является просто нецелесообразным, поэтому используется их родитель UIScrollView, в нашем конкретном случае мы будем использовать SUI аналог - ScrollView, а делать мы будем такую карусель:

1-1.png

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

1.png

Давайте начнем с того что добавим картинки которые я заготовил для вас по этой ссылке https://disk.yandex.ru/d/WeyQ0sotD8Lq0g скачайте и разархивируйте архив с картинками и перенесите их в каталог ассетов как мы это уже делали в предыдущих уроках.

2.png

Теперь наша с вами задача - пользуясь знаниями из прошлых уроков создать карточку продукта

2-2.png

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

Давайте создадим новый файл SwiftUIView, в нем мы будем делать нашу карточку

3.png

Выберите SwiftUI View и назовите файл CardView

3-3.png

Xcode сгенерировал для нас новый файл в котором мы и будем работать

Снимок экрана 2024-02-28 в 10.32.02.png

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

struct CardView: View {
    var body: some View {
		VStack {
			Image("1")
				.resizable()
		}
    }
}

Мы добавили resizable так как мы хотим чтобы впоследствии наша картинка подстраивалась под те контейнеры в которые мы будем его помещать, не пугайтесь что картинка растянулась на все устройство.

Снимок экрана 2024-02-28 в 10.35.48.png

Дальше добавим текст, у нас будет три текстовых элемента (headline, title, caption) обновим наш код под картинкой

struct CardView: View {
    var body: some View {
		VStack {
			Image("1")
				.resizable()
			Text("MacBook D")
				.font(.headline)
				.foregroundStyle(.gray)
			Text("Doubled MacBook Concept")
				.font(.title)
			Text("Created by Oliver Thompson")
				.font(.caption)
				.foregroundStyle(.gray)
		}
    }
}
Снимок экрана 2024-02-28 в 11.29.14.png

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

		VStack(alignment: .leading) {
			Image("1")
				.resizable()
			Text("MacBook D")
				.font(.headline)
				.foregroundStyle(.gray)
			Text("Doubled MacBook Concept")
				.font(.title)
			Text("Created by Oliver Thompson")
				.font(.caption)
				.foregroundStyle(.gray)
		}

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

struct CardView: View {
    var body: some View {
		VStack(alignment: .leading) {
			Image("1")
				.resizable()
			Text("MacBook D")
				.font(.headline)
				.foregroundStyle(.gray)
			Text("Doubled MacBook Concept")
				.font(.title)
			Text("Created by Oliver Thompson")
				.font(.caption)
				.foregroundStyle(.gray)
				.padding(.bottom)
		}
		.clipShape(RoundedRectangle(cornerRadius: 20))
    }
}
Снимок экрана 2024-02-28 в 11.35.51.png

Я добавил clipShape чтобы скруглить весь наш VStack и добавил padding по низу .padding(.bottom) для нашего последнего в списке текста чтобы добиться заданного эффекта. В принципе наша карточка продукта готова, но сейчас она не универсальна, давайте создадим для этой структуры свойства которыми мы будем заполнять наши карточки впоследствии в ContentView

struct CardView: View {
	var imageName: String
	var productType: String
	var productName: String
	var creatorName: String
    var body: some View {
		VStack(alignment: .leading) {
			Image(imageName)
				.resizable()
			Text(productType)
				.font(.headline)
				.foregroundStyle(.gray)
			Text(productName)
				.font(.title)
			Text("Created by \(creatorName)")
				.font(.caption)
				.foregroundStyle(.gray)
				.padding(.bottom)
		}
		.clipShape(RoundedRectangle(cornerRadius: 20))
    }
}

После добавления этих свойств наша превью ругается что ей не хватает данных для рендера

Снимок экрана 2024-02-28 в 11.40.20.png

Давайте добавим эти данные 

#Preview {
	CardView(imageName: "1", productType: "MacBook D", productName: "Doubled MacBook Concept", creatorName: "Oliver Thompson")
}
Снимок экрана 2024-02-28 в 11.41.32.png

Отлично, наша продуктовая карточка готова, давайте добавим четыре копии этих карточек в наш ContentView

struct ContentView: View {
    var body: some View {
        VStack {
            CardView(imageName: "1",
					 productType: "MacBook D",
					 productName: "Doubled MacBook Concep",
					 creatorName: "Oliver Thompson")
			CardView(imageName: "2",
					 productType: "MacBook S",
					 productName: "MacBook S Concept",
					 creatorName: "Emily Bennett")
			CardView(imageName: "3",
					 productType: "MacBook Steam",
					 productName: "SteamPunk MacBook Concept",
					 creatorName: "Haruto Tanaka")
			CardView(imageName: "4",
					 productType: "MacBook S",
					 productName: "Full Sensored MacBook Concept",
					 creatorName: "Yui Sato")
        }
        .padding()
    }
}
Снимок экрана 2024-02-28 в 11.54.04.png

В целом неплохо, но нам точно нужно заставить изображения отображаться в своем разрешении, вернемся в CardView и добавим aspectRatio

struct CardView: View {
	var imageName: String
	var productType: String
	var productName: String
	var creatorName: String
    var body: some View {
		VStack(alignment: .leading) {
			Image(imageName)
				.resizable()
				.aspectRatio(contentMode: .fit)
			Text(productType)
				.font(.headline)
				.foregroundStyle(.gray)
			Text(productName)
				.font(.title)
			Text("Created by \(creatorName)")
				.font(.caption)
				.foregroundStyle(.gray)
				.padding(.bottom)
		}
		.clipShape(RoundedRectangle(cornerRadius: 20))
    }
}
Снимок экрана 2024-02-28 в 11.58.59.png

Наш контент опять съехал, но не переживайте - это связано с тем что в SUI весь контент старается заполнить то пространство которое мы ему даем для заполнения, в текущий момент времени у нас есть вертикальный стек который содержит в себе четыре комплексные карты, он ужимает контент насколько это возможно, как вы видите местами он даже сократил строки текста. Давайте добавим наш VStack в ScrollView

struct ContentView: View {
    var body: some View {
		ScrollView {
			VStack {
				CardView(imageName: "1",
						 productType: "MacBook D",
						 productName: "Doubled MacBook Concept",
						 creatorName: "Oliver Thompson")
				CardView(imageName: "2",
						 productType: "MacBook S",
						 productName: "MacBook S Concept",
						 creatorName: "Emily Bennett")
				CardView(imageName: "3",
						 productType: "MacBook Steam",
						 productName: "SteamPunk MacBook Concept",
						 creatorName: "Haruto Tanaka")
				CardView(imageName: "4",
						 productType: "MacBook S",
						 productName: "Full Sensored MacBook Concept",
						 creatorName: "Yui Sato")
			}
			.padding()
		}
    }
}
Снимок экрана 2024-02-28 в 12.11.42.png

Может показаться странным, но в принципе мы здесь только ради этого и собрались, и на этом вроде как даже все.

Но давайте во первых попробуем посмотреть - что будет если сделать наш скроллвью горизонтальным, а во вторых вы же заметили что у нас есть индикатор скролла, его тоже можно выключить с помощью модификатора scrollIndicators, разумеется если мы хотим чтобы наш контент отображался по горизонтали, то и поместить его следует в HStack

struct ContentView: View {
    var body: some View {
		ScrollView(.horizontal) {
			HStack {
				CardView(imageName: "1",
						 productType: "MacBook D",
						 productName: "Doubled MacBook Concept",
						 creatorName: "Oliver Thompson")
				CardView(imageName: "2",
						 productType: "MacBook S",
						 productName: "MacBook S Concept",
						 creatorName: "Emily Bennett")
				CardView(imageName: "3",
						 productType: "MacBook Steam",
						 productName: "SteamPunk MacBook Concept",
						 creatorName: "Haruto Tanaka")
				CardView(imageName: "4",
						 productType: "MacBook S",
						 productName: "Full Sensored MacBook Concept",
						 creatorName: "Yui Sato")
			}
			.padding()
		}
		.scrollIndicators(.hidden)
    }
}
Снимок экрана 2024-02-28 в 12.19.12.png

Мы добились нужного эффекта и наш скролл работает по горизонтали, но согласитесь сейчас наши картинки заняли слишком много пространства и это не выглядит так удобно как могло бы быть для нашего условного пользователя. В SUI есть еще один элемент который позволяет объединять другие элементы в группу, он так и называется Group, давайте вложим в Group наши CardView и зададим фрейм равный 400 по ширине, таким образом мы получим куда более чистый дизайн, плюс освободится дополнительное пространство, если вы захотите добавить сюда что-то еще.

struct ContentView: View {
    var body: some View {
		ScrollView(.horizontal) {
			HStack {
				Group {
					CardView(imageName: "1",
							 productType: "MacBook D",
							 productName: "Doubled MacBook Concept",
							 creatorName: "Oliver Thompson")
					CardView(imageName: "2",
							 productType: "MacBook S",
							 productName: "MacBook S Concept",
							 creatorName: "Emily Bennett")
					CardView(imageName: "3",
							 productType: "MacBook Steam",
							 productName: "SteamPunk MacBook Concept",
							 creatorName: "Haruto Tanaka")
					CardView(imageName: "4",
							 productType: "MacBook S",
							 productName: "Full Sensored MacBook Concept",
							 creatorName: "Yui Sato")
				}
				.frame(width: 400)
				.padding()
			}
		}
		.scrollIndicators(.hidden)
    }
}
Снимок экрана 2024-02-28 в 12.30.04.png

На этом можно было бы и заканчивать, но как вы знаете у ScrollView есть возможность скролить контент не только вертикально и горизонтально, но и по обеим осям сразу, для этого нам потребуется всего лишь передать в инициализатор массив из обеих осей - поэтому можете удалить весь код который написали выше и заменить его на следующий просто чтобы протестировать такое поведение:

struct ContentView: View {
	var body: some View {
		ScrollView([.horizontal, .vertical]) {
			Image(systemName: "macbook")
				.font(.system(size: 999))
		}
	}
}
Снимок экрана 2024-02-28 в 12.35.15.png

Теперь контент можно скролить так как вам вздумается по всем доступным нам осям одновременно.

Итоги

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

Как и прежде подписывайтесь на мой телеграм канал - https://t.me/swiftexplorer

Буду рад вашим комментариям и лайкам!

Спасибо за прочтение!

Комментарии (0)