TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Alternative to offsets

Forums > SwiftUI

Hi, it's me again on a similar topic. By GOD'S Grace, the interface worked out well for iOS.

But now, I am trying to build the same app using SKIP. It uses the SwiftUI code and sort of converts it to Android. But, it does not support Geometry Reader. So I have had to use offsets.

Please, is there another way to construct something like this without using many offsets and Geometry Reader?

My subtitle is not aligning with the other HEADER as it should, please

Will attach screenshots too. Thanks for everything once again and GOD bless you all.


import SwiftUI
public struct ContentView: View {

    public init() {
    }

    public var body: some View {
        VStack {
            ZStack(alignment: .bottomLeading) {
                // This may be your header image
                Color.orange.opacity(0.2)
                    .frame(height: 200)
**_//OFFSET HERE_**  
                    .offset(y: -70)

                VStack(alignment: .leading, spacing: 10) {

                    Text("SOME HEADER TEXT")
                        .font(
                            Font.custom("Avenir Next Heavy", size: 20))

                    Text("Subtitle goes here...")
                        .font(
                            Font.custom("Avenir Next Regular", size: 17))

                        .frame(maxWidth: .infinity)
                                      }
                                      .padding(.horizontal, 16)
                                      .padding(.top, 60)
                                      .padding(.bottom, 16)
                                      .frame(maxWidth: .infinity, alignment: .leading)
                                      .background(
                                          RoundedRectangle(cornerRadius: 20)
                                              .foregroundColor(.white)
                                              .shadow(radius: 5)
                                      )
                                      .overlay {
                                Circle()
                            .foregroundStyle(.purple)
                    .frame(width: 70, height: 70)
**_//OFFSET HERE_**        

  .offset(x:-150, y:-90)
                                }

                                                  }
            TabView {
                           //HomeView
                           HomeView()
                               .tabItem {
                                   Text("Home")
                                   Image(systemName: "house.circle")
#if SKIP
                               Image(systemName: "Icons.Filled.Home")
#endif
                               }
                           //MediaView
                           MediaView()
                               .tabItem {
                                   Text("Media")
                               Image(systemName: "tv.and.mediabox")
#if SKIP
                               Image(systemName: "Icons.Outlined.KeyboardArrowDown")
#endif
                               }
                           //VideosView
                           VideosView()
                               .tabItem {
                                   Text("Videos")
                               Image(systemName: "video")
#if SKIP
                        Image(systemName: "Icons.Outlined.PlayArrow")
    #endif
                               }
                           //ContactView
                           ContactView()
                               .tabItem {
                                   Text("Contact")
                               Image(systemName: "person.crop.circle.fill")
#if SKIP
                    Image(systemName: "Icons.Filled.AccountCircle")
#endif
                               }
                       }
                   }

            }
        }

#Preview {
    ContentView()
}

Screenshot_1714421422

   

Try to use alignmentGuides like so:

.overlay(alignment: .topLeading) {
    ZStack {
        Circle()
            .stroke(Color.gray, lineWidth: 5)
        Circle()
            .fill(.purple)
    }
    .frame(width: 70, height: 70)
    .alignmentGuide(.top) { $0[.top] + $0.height / 2 }
    .alignmentGuide(.leading) { $0[.leading] - $0.width / 3 }
}

   

Just to remind you of what we were working on before, please:

struct ContentView: View {
    var body: some View {
        VStack {
            ZStack(alignment: .bottomLeading) {
                // This may be your header image
                Color.orange.opacity(0.2)
                    .frame(height: 300)
                    .offset(y: -20)

                VStack(alignment: .leading, spacing: 10) {
                    Text("SOME HEADER TEXT")
                        .bold()

                    Text("Subtitle...")

                    HStack(spacing: 60) {
                        Button(action: { }, label: {
                            Label("Invite", systemImage: "person")
                        })
                        .buttonStyle(.borderedProminent)
                        .tint(.purple)

                        Button(action: { }, label: {
                            Label("Contact Us", systemImage: "envelope")
                        })
                        .buttonStyle(.borderedProminent)
                        .tint(.purple)
                    }
                    .frame(maxWidth: .infinity)

                }
                .padding(.horizontal, 16)
                .padding(.top, 60)
                .padding(.bottom, 16)
                .frame(maxWidth: .infinity, alignment: .leading)
                .background(
                    RoundedRectangle(cornerRadius: 20)
                        .foregroundColor(.white)
                        .shadow(radius: 5)
                )
                .overlay(
                    GeometryReader { geometry in
                        Circle()
                            .foregroundStyle(.purple)
                            .frame(width: 70, height: 70)
                            .position(x: geometry.size.width * 0.1, y: geometry.size.height * 0.05)
                    }
                )
            }

            TabView {

                //HomeView
                HomeView()
                    .tabItem {
                        Label("Home", systemImage: "house.circle")
                    }
                //MediaView
                MediaView()
                    .tabItem {
                        Label("Media", systemImage: "tv.and.mediabox")
                    }
                //VideosView
                VideosView()
                    .tabItem {
                        Label("Videos", systemImage: "video")
                    }
                //ContactView
                ContactView()
                    .tabItem {
                        Label("Contact", systemImage: "person.crop.circle.fill")

                    }
            }

        }

        //Tab Customisation
        .onAppear() {

            UITabBar.appearance().backgroundColor = .purple
            UITabBar.appearance().unselectedItemTintColor = .systemRed

        }
        .tint(.white)
        .ignoresSafeArea()
    }
}

   

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

Sponsor Hacking with Swift and reach the world's largest Swift community!

Without original image it is difficult to replicate. As it it the image of certain size that might cause the problem.

   

You may want to place header, text + buttons inside another VStack and give a negative spacing like so:

 VStack(spacing: -12) {
    headerBackgroundImage

    VStack(alignment: .leading, spacing: 10) {
        headerText
        horizontalButtonsStack
    }
    .padding(.horizontal, 16)
    .padding(.top, 60)
    .padding(.bottom, 16)
    .frame(maxWidth: .infinity, alignment: .leading)
    .background(
        RoundedRectangle(cornerRadius: 10)
            .foregroundColor(.white)
            .shadow(radius: 5)
    )
    .overlay(alignment: .topLeading) {
        ZStack {
            Circle()
                .stroke(Color.gray, lineWidth: 5)
            Circle()
                .fill(.purple)
        }
        .frame(width: 70, height: 70)
        .alignmentGuide(.top) { $0[.top] + $0.height / 2 }
        .alignmentGuide(.leading) { $0[.leading] - $0.width / 3 }
    }
}

and for the picture to use that var:

private var headerBackgroundImage: some View {
    Image(.header)
        .resizable()
        .scaledToFit()
}

so it works on all devices well.

   

Thanks so much. So I understand better, should I replace Image(.header) with the picture's name, please?

   

Yes.

   

Yikes... What have I done? Please, I will send you my old code file, and the new code file as I tried to insert yours...

I think I jinxed it, but, I am so stressed right now to do anything or think about it... :'(

   

In your new code you have this line which is supposed to be the image.

Color.purple
      .ignoresSafeArea()

you have to add a VStack and put that image as well as all the header text and horizontal buttons like so:

VStack(spacing: -12) {
    //This your image view

    // This is your header text and two buttons
    )
    .overlay(alignment: .topLeading) {
        ZStack {
            // This is your logo
        }
        .frame(width: 70, height: 70)
        .alignmentGuide(.top) { $0[.top] + $0.height / 2 }
        .alignmentGuide(.leading) { $0[.leading] - $0.width / 3 }
    }
}

   

I keep trying but get some errors. So sorry for troubling you so :|

Could you kindly input it in the New ContentView for me, you could leave the purple so I will replace it.

And my popup for the "Invite" button seems to appear weirdly on the iPad view

So sorry to ask this of you... Thanks so much.

   

To make it short replace zstack... But this code needs definite refactoring... sorry but this type is called noodle code... it is really difficult to read it... you have to separate it least some parts of it...

As for buttons, work needs to be done to make it work on other devices as well.

ZStack(alignment: .bottomLeading) {
    VStack(spacing: -15) {
        // Header Image
        Image("Header")
            .resizable()
            .scaledToFit()

        // Header Text
        VStack(alignment: .leading, spacing: 10) {

            Text("HEAVEN SEEKERS MIN.")
                .font(
                    Font.custom("Avenir Next", size: 20))
                .bold()

            Text("The Undiluted Gospel...")
                .font(
                    Font.custom("Avenir Next", size: 17))

            // BUTTONS

            // INVITE BUTTON

            HStack(spacing: 35) {

                Button {
                    showingPopover = true
                } label: {
                    Image(systemName: "person.badge.plus.fill")
                    Text("Invite    ")
                        .padding(.horizontal)
                        .font(Font.custom("Avenir Next", size: 17))
                }
                .padding()
                .foregroundColor(Color.purple)
                .background(
                    RoundedRectangle(cornerRadius: 100)
                        .stroke(Color.purple, lineWidth: 2))
                .cornerRadius(100)
                //Invite Popover
                .popover(isPresented: $showingPopover) {
                    invitePopover
                    Spacer()
                }

                // CONTACT BUTTON
                Button{
                    sendEmail.toggle()
                } label: {
                    Image(systemName: "envelope.open")
                    Text("Contact Us")
                }
                .padding(.horizontal, 2)
                .font(Font.custom("Avenir Next", size: 16))
                .padding()
                .foregroundColor(Color.white)
                .background(Color.purple)
                .cornerRadius(100)
            }
            .frame(maxWidth: .infinity)

        }

        //WHITE RECTANGLE
        .padding(.horizontal, 16)
        .padding(.top, 60)
        .padding(.bottom, 16)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(
            RoundedRectangle(cornerRadius: 20)
                .foregroundColor(.white)
                .shadow(radius: 5)
        )

        //LOGO

        .overlay(
            GeometryReader { geometry in
                Image("logo")
                    .resizable()
                    .foregroundStyle(.purple)
                    .frame(width: 70, height: 70)
                    .position(x: geometry.size.width * 0.1, y: geometry.size.height * 0.05)
            }
        )
    }
}
.ignoresSafeArea()

   

note that invitePopover code was moved out to extension like so:

extension ContentView {
    private var invitePopover: some View {
        ZStack{
            Color.purple
                .ignoresSafeArea()

            HStack {
                VStack {
                    List {
                        //Head Text
                        Text("Invite Members")
                            .font( Font.custom("Avenir Next", size: 23))
                            .bold()
                            .foregroundColor(Color.white)
                            .listRowSeparator(.hidden)
                            .listRowBackground(Color.purple)

                        //Sub Text
                        Text("Invite people to join you in the app!")
                            .font(Font.custom("Avenir Next", size: 15))
                            .italic()
                            .foregroundColor(Color.white)
                            .listRowSeparatorTint(.white)
                            .listRowBackground(Color.purple)

                        //Share Link
                        ShareLink(item: URL(string: "https://apps.apple.com/gh/app/heaven-seekers-ministry/id1605769086")!) {Label("Share via social & more", systemImage: "square.and.arrow.up") }
                            .listRowBackground(Color.purple)
                            .bold()
                            .font(Font.custom("Avenir Next", size: 20))

                        //COPY LINK
                        //Popup Message
                        if isCopied {
                            Text("Copied Successfully!")
                                .bold()
                                .font(Font.custom("Avenir Next", size: 20))
                                .frame(width: 440, height: 10)
                                .foregroundColor(.purple)
                                .overlay(
                                    RoundedRectangle(cornerRadius: 20)
                                        .padding()
                                )
                        }

                        //COPY CODE
                        Text("\(Image(systemName: "link.circle.fill"))    Copy Link")
                            .listRowBackground(Color.purple)
                            .bold()
                            .font(Font.custom("Avenir Next", size: 20))
                            .onTapGesture {
                                let clipboard = UIPasteboard.general
                                clipboard.setValue(address, forPasteboardType: UTType.plainText.identifier)

                                isCopied = true
                                DispatchQueue.main.asyncAfter(wallDeadline: .now() + 3) {
                                    withAnimation {
                                        isCopied = false
                                    } }}}
                    .background(.purple).scrollContentBackground(.hidden)
                    .foregroundColor(Color.white)
                    .presentationDetents([.height(250)])
                    .presentationBackground(Color.purple)
                }}}
    }
}

   

I noticed it... and wish it could look neater. I tried putting the Comments in to help, but it all seems a mess. So sorry. Is there anything I can do to make it more readable/neat?

And, please was this code put in the file I sent or I should copy it from here?

Many thanks for all the good work done. You are a GOD sent.

   

Oh, just saw the second comment, thank you!

   

Here is, to certain extent refactored code. Though, I placed all in content view as you can see you can separate to different files. At least the body property is more readable now.

I hope I won't break too much in your code, as there was so much that seems to be redundant in the original code especially with offsets and frames. So to make the long story short i placed that in github so you can copy and just play around with that. Don't want to dumb long lines here, I will have a look at popover again a bit later as it works on iPad now, but definitely there is a better way to handle that...

https://github.com/ygeras/CustomizedTabbarApperance.git

   

I also have made some slick way for popover, should you wish to try it out let me know I will push update via github.

   

Wow, I do not know what to say. Thank you so much! And yes please, I would love that.

   

I have pushed changes.

For image in nav bar it should be customized which takes additional logic and time. I am not sure I can do it today, maybe tomorrow or other day... But for contact us button, it opens a sheet with contact form.

UPDATE. Header in mail view also cusomized to have an image.

   

If you'd like i can push another change, as i didn't really liked copy animation on previous version, now it shows that it is done in the middle. Here how it looks like now.

   

Beautiful, I also did not like the "Copied" animation from befofe. GOD bless you so much. I love it. XD

   

Have you changed the "Copied" yet, please?

And also, could you kindly help me transfer the FloatingButtonsView and ButtonLabel to new files? Xcode is giving me some funny errors when I do so. XD

   

The changes are pushed. Code was refactored so that screens are not so cluttered. Redundant and not used parts of code removed. So clone from github and adjust to your needs.

   

Wow... okay so... sorry for the dumb question, but, what of the ContentView?

Will it link to all these folders? I am hoping to copy them one after the other and create the folders so as not to confuse myself when trying to work on any of them. XD

But, everything, even the one from yesterday, is working soooooo well. GOD bless you so much

sending virtual hugs

   

I just changed it to MainView. If you go to MainView.swift and right click on MainView struct -> Refactor -> Rename and change the name to whatever you want, so that it will change everywhere esle and will continue working. And as for file structures it is absolutely up to you where and how you want to place them, but make it so it is clear for everone and mostly for future self, should you wish to update it later )))

Welcome!

   

Are you an Angel? XD

Amazing!!! GOD bless you so, so much!!!!!!

I will let you know when I am done copying so we can remove some private stuff, (Images, links) as it is a bit confidential.

I hope you get me, please, and are not bothered. :)

   

Let me know when done, so I will remove repo from github )

1      

I'm done copying!!! XD

Thanks so much once again. By GOD'S Grace all is running very well.

Btw, what does "Error acquiring assertion: <Error Domain=RBSServiceErrorDomain Code=1 "(originator doesn't have entitlement com.apple.runningboard.assertions.webkit AND originator doesn't have entitlement com.apple.multitasking.systemappassertions)" UserInfo={NSLocalizedFailureReason=(originator doesn't have entitlement com.apple.runningboard.assertions.webkit AND originator doesn't have entitlement com.apple.multitasking.systemappassertions)}>** 0x11f012520 - ProcessAssertion::acquireSync Failed to acquire RBS assertion 'WebKit Media Playback' for process with PID=1,155, error:" mean?

It pops up in console when I try to play the YouTube videos there. They play fine though.

And, I wonder why my Firebase notification pops up to ask for permission to send updates to my iPhone only and not my iPad as well... hmm...

   

Sounds good, then deleting repo if no longer needed. As for the errors, try to google around, sometimes it's so called "noise" errors, not really related to any issues. The same for Firebase, not sure about workflow in that part :)

1      

OK!!!

Now, I am on to the next task... trying to convert these for Android using SKIP.tools

It uses SwiftUI to build the Android version, but does not yet support all the functions in SwiftUI :(

By GOD'S Grace, going to try my best with what you have given me and see. :)

GOD bless you!!! Though I do not really know you, you have been a gift. I appreciate it a ton

   

GOD bless you!!! Though I do not really know you, you have been a gift. I appreciate it a ton

always pleasure :)

rying to convert these for Android using SKIP.tools

good luck with the challenge. you CAN do that :)

   

Aww... I am grateful :')

   

@ygeras Oh, and GOD bless you so much. Apple approved the app. I am so grateful. :')

   

Do you perhaps think it is possible to edit,ButtonViewsConsolidated, FloatingButtonsView, and InviteView with these new constraints at hand?

github page is missing...

   

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.