My App is working, but there is a lot of repetative code.
I can't figure out how to refactor the GeometryReader also how could I change the code using a ForEach to comply with MVVM Design Pattern. Last should this be put into a vertical ScrollView?
Any direction would be great to learn how to write cleaner Swift code.
import SwiftUI
struct ContentView: View {
let colorFriendship = LinearGradient(colors: [Color("ColorFriendshipLight"), Color("ColorFriendshipDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
let colorWealth = LinearGradient(colors: [Color("ColorWealthLight"), Color("ColorWealthDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
let colorEducation = LinearGradient(colors: [Color("ColorEducationLight"), Color("ColorEducationDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
let colorCareer = LinearGradient(colors: [Color("ColorCareerLight"), Color("ColorCareerDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
let colorFamily = LinearGradient(colors: [Color("ColorFamilyLight"), Color("ColorFamilyDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
let colorHealth = LinearGradient(colors: [Color("ColorHealthLight"), Color("ColorHealthDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
let colorSpirituality = LinearGradient(colors: [Color("ColorSpiritualityLight"), Color("ColorSpiritualityDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
let colorCompose = LinearGradient(colors: [Color("ColorComposeLight"), Color("ColorComposeDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
// Shadow Icons
private var radius = 7
private var xOffset = 6
private var yOffset = 6
var body: some View {
VStack {
NavigationView {
VStack {
HStack {
NavigationLink(destination: FriendshipListView()) {
VStack(alignment: .center) {
GeometryReader { geo in
Image("menu-icon-friendship")
.resizable()
.scaledToFit()
.frame(
width: geo.size.width,
height: geo.size.height)
.shadow(color: Color("ColorCareerDark"), radius: CGFloat(radius), x: CGFloat(xOffset), y: CGFloat(yOffset))
}
Text("Friendships")
.titleStyle()
}
.frame(maxWidth: .infinity, maxHeight: 110)
.padding(20)
.background(colorFriendship)
.cornerRadius(20)
}
NavigationLink(destination: WealthListView()) {
VStack(alignment: .center) {
GeometryReader { geo in
Image("menu-icon-wealth")
.resizable()
.scaledToFit()
.frame(
width: geo.size.width,
height: geo.size.height)
.shadow(color: Color("ColorWealthDark"), radius: CGFloat(radius), x: CGFloat(xOffset), y: CGFloat(yOffset))
}
Text("Wealth")
.titleStyle()
}
.frame(maxWidth: .infinity, maxHeight: 110)
.padding(20)
.background(colorWealth)
.cornerRadius(20)
}
}
HStack {
NavigationLink(destination: EducationListView()) {
VStack(alignment: .center) {
GeometryReader { geo in
Image("menu-icon-education")
.resizable()
.scaledToFit()
.frame(
width: geo.size.width,
height: geo.size.height)
.shadow(color: Color("ColorEducationDark"), radius: CGFloat(radius), x: CGFloat(xOffset), y: CGFloat(yOffset))
}
Text("Education")
.titleStyle()
}
.frame(maxWidth: .infinity, maxHeight: 110)
.padding(30)
.background(colorEducation)
.cornerRadius(20)
}
NavigationLink(destination: CareerListView()) {
VStack(alignment: .center) {
GeometryReader { geo in
Image("menu-icon-career")
.resizable()
.scaledToFit()
.frame(
width: geo.size.width,
height: geo.size.height)
.shadow(color: Color("ColorCareerDark"), radius: CGFloat(radius), x: CGFloat(xOffset), y: CGFloat(yOffset))
}
Text("Career")
.titleStyle()
}
.frame(maxWidth: .infinity, maxHeight: 110)
.padding(30)
.background(colorCareer)
.cornerRadius(20)
}
}
HStack {
NavigationLink(destination: FamilyListView()) {
VStack(alignment: .center) {
GeometryReader { geo in
Image("menu-icon-family")
.resizable()
.scaledToFit()
.frame(
width: geo.size.width,
height: geo.size.height)
.shadow(color: Color("ColorFamilyDark"), radius: CGFloat(radius), x: CGFloat(xOffset), y: CGFloat(yOffset))
}
Text("Family")
.titleStyle()
}
.frame(maxWidth: .infinity, maxHeight: 110)
.padding(30)
.background(colorFamily)
.cornerRadius(20)
}
NavigationLink(destination: HealthListView()) {
VStack(alignment: .center) {
GeometryReader { geo in
Image("menu-icon-health")
.resizable()
.scaledToFit()
.frame(
width: geo.size.width,
height: geo.size.height)
.shadow(color: Color("ColorHealthDark"), radius: CGFloat(radius), x: CGFloat(xOffset), y: CGFloat(yOffset))
}
Text("Health")
.titleStyle()
}
.frame(maxWidth: .infinity, maxHeight: 110)
.padding(30)
.background(colorHealth)
.cornerRadius(20)
}
}
HStack {
NavigationLink(destination: SpiritualityListView()) {
VStack(alignment: .center) {
GeometryReader { geo in
Image("menu-icon-spirituality")
.resizable()
.scaledToFit()
.frame(
width: geo.size.width,
height: geo.size.height)
.shadow(color: Color("ColorSpiritualityDark"), radius: CGFloat(radius), x: CGFloat(xOffset), y: CGFloat(yOffset))
}
Text("Spirituality")
.titleStyle()
}
.frame(maxWidth: .infinity, maxHeight: 110)
.padding(30)
.background(colorSpirituality)
.cornerRadius(20)
}
NavigationLink(destination: ComposeListView()) {
VStack(alignment: .center) {
GeometryReader { geo in
Image("menu-icon-compose")
.resizable()
.scaledToFit()
.frame(
width: geo.size.width,
height: geo.size.height)
.shadow(color: Color("ColorSpiritualityDark"), radius: CGFloat(radius), x: CGFloat(xOffset), y: CGFloat(yOffset))
}
Text("Compose")
.titleStyle()
}
.frame(maxWidth: .infinity, maxHeight: 110)
.padding(30)
.background(colorCompose)
.cornerRadius(20)
}
}
}
.navigationTitle("Main Menu")
}
}
.padding(.horizontal)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// Custom modifiers
// MenuTitle
struct MenuTitle: ViewModifier {
func body(content: Content) -> some View {
content
.font(.system(.title2, design: .rounded))
.foregroundColor(.white)
.fontWeight(.regular)
.shadow(color: Color(.black), radius: 7, x: 2, y: 2)
}
}
extension View {
func titleStyle() -> some View {
modifier(MenuTitle())
}
}
// Struct List Views
struct FriendshipListView:View {
var body: some View {
Text("Friendship List")
}
}
struct WealthListView:View {
var body: some View {
Text("Wealth List")
}
}
struct EducationListView:View {
var body: some View {
Text("Education List")
}
}
struct CareerListView:View {
var body: some View {
Text("Career List")
}
}
struct FamilyListView:View {
var body: some View {
Text("Family List")
}
}
struct HealthListView:View {
var body: some View {
Text("Health List")
}
}
struct SpiritualityListView:View {
var body: some View {
Text("Spirituality List")
}
}
struct ComposeListView:View {
var body: some View {
Text("Compose List")
}
}
CodePudding user response:
To restructure this you would first need to create some sort of datamodel that holds the different informations related to each topic you want to display.
Disclaimer:
I´m not going to post a complete working example. So no need in asking for one. But I will try to point you in the correct direction.
First create an enum that defines all the different topics you want to display. I implemented only 2 cases and not all properties but the idea behind it should become clear.
enum Topic: CaseIterable, Identifiable{
// all the topics
case friendship, wealth
// to conform to Identifiable
var id: Topic { self }
// create the background gradient
var background: LinearGradient{
switch self{
case .friendship:
return LinearGradient(colors: [Color("ColorFriendshipLight"), Color("ColorFriendshipDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
case .wealth:
return LinearGradient(colors: [Color("ColorWealthLight"), Color("ColorWealthDark")], startPoint: .topLeading, endPoint: .bottomTrailing)
}
}
// the image names
var imagename: String{
switch self{
case .friendship:
return "menu-icon-friendship"
case .wealth:
return "menu-icon-wealth"
}
}
// here you create the target view. If the views need to get properties set, you can give your enum cases assosiated values and add them during initialization
@ViewBuilder
var targetView: some View{
switch self{
case .friendship:
FriendshipListView()
case .wealth:
WealthListView()
}
}
}
and using a LazyVGrid
the Views become pretty simple:
struct ContentView: View{
let columns = [GridItem(.flexible()), .init(.flexible())]
var body: some View{
NavigationView{
ScrollView{
LazyVGrid(columns: columns) {
// iterate over all topics
ForEach(Topic.allCases){ topic in
// create you reusable view and pass the data into it
CardView(topic: topic)
}
}
}
}
}
}
// this view will be reused for displayin the topics
struct CardView: View{
let topic: Topic
// Shadow Icons
private let radius = 7
private let xOffset = 6
private let yOffset = 6
var body: some View{
NavigationLink(destination: topic.targetView) {
VStack(alignment: .center) {
// I don´t think you need the geometry reader here but
// I don´t know how your view will look without it
GeometryReader { geo in
Image(topic.imagename)
.resizable()
.scaledToFit()
.frame(
width: geo.size.width,
height: geo.size.height)
.shadow(color: topic.shadowColor, radius: CGFloat(radius), x: CGFloat(xOffset), y: CGFloat(yOffset))
}
Text(topic.title)
.titleStyle()
}
.frame(maxWidth: .infinity, maxHeight: 110)
.padding(30)
.background(topic.background)
.cornerRadius(20)
}
}
}