Skip to content

Instantly share code, notes, and snippets.

@byJeevan
Last active September 14, 2024 17:33
Show Gist options
  • Save byJeevan/f67485bf380e86d4f87a723ac383827c to your computer and use it in GitHub Desktop.
Save byJeevan/f67485bf380e86d4f87a723ac383827c to your computer and use it in GitHub Desktop.
SwiftUI Components & Extension that saves iOSDev time.
### UIKit is **EVENT-Driven** framework - We could reference each view in the hierarchy, update its appearance when the view is loaded or as a reaction on an event.
### SwiftUI is **Declarative, State Driven** framework - We cannot reference any view in the hierarchy, neither can we directly mutate a view as a reaction to an event.
Instead, we mutate the state bound to the view. Delegates, target-actions, responder chain, KVO .. replaced with Closures & bindings.
@byJeevan
Copy link
Author

byJeevan commented Jun 8, 2020

// SwiftUI Button

    // Button - Fill Width
        Button(action: {
            print("Button tapped!")
        }) {
            Text("LOGIN")
                 .fontWeight(.bold)
                .font(.headline)
                .frame(minWidth: 100, maxWidth: .infinity, minHeight: 50)
                .background(Color.red)
                .foregroundColor(Color.white)
                .cornerRadius(25.0)
        }
        
  // Button - FIT Title Width
        Button(action: {
            print("Login tapped!")
        }) {
            HStack {
                Text("LOGIN")
                    .fontWeight(.bold)
                    .font(.headline)
            }                
        }
        .padding()
        .background(Color.red)
        .foregroundColor(Color.white)
        .cornerRadius(.infinity)

@byJeevan
Copy link
Author

byJeevan commented Jun 8, 2020

Color Extension :

/// Hex to Color converter. Usage eg. Color(hex: 0x363CC) or Color(hexString: "#FF0000")
extension Color {
  
  init(hexString: String, alpha: Double = 1.0) {
    var cString = hexString.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
    
    if cString.hasPrefix("#") { cString.removeFirst() }
    
    if cString.count == 6 {
      var rgbValue: UInt64 = 0
      Scanner(string: cString).scanHexInt64(&rgbValue)
      
      self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
                green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
                blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
                opacity: alpha)
    } else {
      self.init(hexString: "#000000")
    }
  }
  
  init(hex: Int, alpha: Double = 1.0) {
    let components = (
      R: Double((hex >> 16) & 0xff) / 255,
      G: Double((hex >> 08) & 0xff) / 255,
      B: Double((hex >> 00) & 0xff) / 255
    )
    self.init(.sRGB, red: components.R, green: components.G, blue: components.B, opacity: alpha)
  }
}

@byJeevan
Copy link
Author

byJeevan commented Jul 4, 2020

/* CARD SHADOW for any view */

<your view >.background(
                           RoundedRectangle(cornerRadius: 15)
                               .foregroundColor(Color.white)
                               .shadow( color: Color.gray.opacity(0.35),radius: 15, x: 0, y: 0))

@byJeevan
Copy link
Author

byJeevan commented Jul 18, 2020

/* Full Screen */

  <you view>
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        .background(Color.red)
        .edgesIgnoringSafeArea(.all)

@byJeevan
Copy link
Author

byJeevan commented Aug 9, 2020

//Navigation bar hidden for a view

    NavigationView {
        
        ZStack {
            
      <your content view eleemnts>
        }
        .navigationBarTitle("Hidden Title")
        .navigationBarHidden(self.isNavigationBarHidden)
        .onAppear {
            self.isNavigationBarHidden = true
        }
        
        
    }

@byJeevan
Copy link
Author

byJeevan commented Aug 9, 2020

//Share Sheet

struct ShareSheet: UIViewControllerRepresentable {
    typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
    
    let activityItems: [Any]
    let applicationActivities: [UIActivity]? = nil
    let excludedActivityTypes: [UIActivity.ActivityType]? = nil
    let callback: Callback? = nil
    
    func makeUIViewController(context: Context) -> UIActivityViewController {
        let controller = UIActivityViewController(
            activityItems: activityItems,
            applicationActivities: applicationActivities)
        controller.excludedActivityTypes = excludedActivityTypes
        controller.completionWithItemsHandler = callback
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
        // nothing to do here
    }
}

//Usage :
struct ContentView: View {
    @State private var showShareSheet = false
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Hello World")
            Button(action: {
                self.showShareSheet = true
            }) {
                Text("Share Me").bold()
            }
        }
        .sheet(isPresented: $showShareSheet) {
            ShareSheet(activityItems: ["Hello World"])
        }
    }
}

@byJeevan
Copy link
Author

Width of image = Screen width & aspect ratio retained (height)

 Image("workout-image")
            .resizable()
            .scaledToFit()

@byJeevan
Copy link
Author

byJeevan commented Oct 28, 2020

 let detectDirectionalDrags = DragGesture(minimumDistance: 3.0, coordinateSpace: .local)
    .onEnded { value in
        print(value.translation)
        
        if value.translation.width < 0 && value.translation.height > -30 && value.translation.height < 30 {
            print("left swipe")
        }
        else if value.translation.width > 0 && value.translation.height > -30 && value.translation.height < 30 {
            print("right swipe")
        }
        else if value.translation.height < 0 && value.translation.width < 100 && value.translation.width > -100 {
            print("up swipe")
        }
        else if value.translation.height > 0 && value.translation.width < 100 && value.translation.width > -100 {
            print("down swipe")
        }
        else {
            print("no clue")
        }
    }

usage .guesture(detectDirectionalDrags)

@byJeevan
Copy link
Author

byJeevan commented Nov 14, 2020

Corner Radius in specific corner

.background(Rectangle().cornerRadius(40.0, corners: [.topLeft, .topRight]).foregroundColor(Color(0xF4F5FC)))

extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape( RoundedCorner(radius: radius, corners: corners) )
    }
}

struct RoundedCorner: Shape {

    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

@byJeevan
Copy link
Author

byJeevan commented Nov 14, 2020

What SwiftUI Transitions Are Available ?
There are 5 transitions available in SwiftUI:

  1. Slide - slide in the view from the leading side and remove it towards the trailing side.
  2. Move - slide in from the direction specified and remove it from the same direction.
  3. Opacity - fade in the view when inserted and fade out the view when removed.
  4. Scale - insert the view FROM the scale specified and reverse the effect when removed.
  5. Offset - like the move transition by you can set your own x and y coordinate.

Choices for applying an animation?

  1. Apply an animation directly to the view.
  2. Apply an animation to the parent view.
  3. Use an explicit animation (withAnimation closure) around the variable change that controls when the view is inserted/removed.

⚠️ As of this writing, the opacity and scale only work with explicit animations (withAnimation).

@byJeevan
Copy link
Author

byJeevan commented Mar 1, 2021

Creating a List with Navigation Link
Note the hierarchy : NavigationView >> List >> NavigationLink

        NavigationView {
            List(1...10, id: \.self) { index in
                
                if index == 1 {
                    //do something for row = 1
                }
                else{
                    NavigationLink(
                        destination: Text("Item #\(index) Details"),
                        label: {
                            Text("Item #\(index)")
                                .font(.system(size: 20, weight: .bold, design: .rounded))
                        })
                }
            
            }
            
            .navigationTitle("Latest News")
        }

@byJeevan
Copy link
Author

byJeevan commented Mar 1, 2021

Tabbar/ TabView creation - VStack as parent.

       VStack {
           
           TabView {
             
               
               Text("Video Tab")
                   .font(.system(size: 30, weight: .bold, design: .rounded))
                   .tabItem {
                       Image(systemName: "video.circle.fill")
                       Text("Live")
                   }
               
        
                
               Text("Profile Tab")
                   .font(.system(size: 30, weight: .bold, design: .rounded))
                   .tabItem {
                       Image(systemName: "person.crop.circle")
                       Text("Profile")
                   }
           }
           .accentColor(.red)
           .onAppear() {
               UITabBar.appearance().barTintColor = .white
           }
       }

Tint color and selection/highlight color of tab changed.

@byJeevan
Copy link
Author

byJeevan commented May 15, 2021

Click/Tap/Select/Touch event to view:

    @State private var isPresented = false

<your view>.gesture(DragGesture(minimumDistance: 0)
                                                        .onChanged { value in
                                                            if isPresented {
                                                                print("Touch down")
                                                            }
                                                            isPresented = false
                                                        }
                                                        .onEnded { value in
                                                            print("Touch up")
                                                            isPresented = true
                                                        }
                                                )
                                            

@byJeevan
Copy link
Author

byJeevan commented Nov 4, 2021

.navigationBarHidden(true) is not constructed for NavigationView instead, it should be it's child.

@byJeevan
Copy link
Author

byJeevan commented Nov 4, 2021

Return a view from a function - that checks some conditions.
PS: return types : some View vs AnyView vs View
Ref: https://stackoverflow.com/questions/62895948/how-to-return-a-view-type-from-a-function-in-swift-ui

@ViewBuilder func WidgetDetail(wOption: WidgetOptions) -> some View {
        switch wOption.id {
        case 0:
            CountryPicker()
        default:
            Text("Widget Not implemented")
        }
    }

@byJeevan
Copy link
Author

byJeevan commented Nov 5, 2021

Present view Modally (fullscreen) [ for iOS 14.0+]

         @State var isQuizPlayPresented = false
            
            Button(action: {
                  isQuizPlayPresented.toggle()
              }) {
                  Text("Get Started")
              }
              .padding()
              .fullScreenCover(isPresented: $isQuizPlayPresented, onDismiss: nil, content: QuizPlayView.init)

Dismiss a presented view (modally)

    @Environment(\.presentationMode) private var presentationMode // define
     self.presentationMode.wrappedValue.dismiss() // In button action

Advanced:

            .fullScreenCover(isPresented: $isResultView,
                             onDismiss: {
                self.presentationMode.wrappedValue.dismiss() // Dismisses current view along with Child view dismissed
            }, content: {
                QuizResultView(totalScore: 10) // Pass custom parameter to child view
            })

@byJeevan
Copy link
Author

Make the width of element fill till parent width

        VStack(alignment: .center) {
          Rectangle()
            .frame(height: 0.0) // -------- note this 
           Text("SHOW DETAILS")
        }
        .background(.yellow)
        

@byJeevan
Copy link
Author

byJeevan commented Apr 11, 2024

Basic Rail :

struct QuizRail: View {
  
  var body: some View {
    ScrollView(.horizontal) {
      HStack(spacing: 16) {
        ForEach((1...10), id: \.self) {_ in
          Rectangle()
            .frame(width: 150, height: 200)
            .cornerRadius(8)
        }
      }
      .padding(.horizontal, 16)
    }
  }
}

@byJeevan
Copy link
Author

List without separator (from iOS 15.0+)

          List {
            ForEach(items, id: \.self) { item in
              Text("item here")
                .listRowSeparator(.hidden)

            }
          }
          .listStyle(.plain)

@byJeevan
Copy link
Author

☢️ A workaround calculated the height of ScrollView

  @State var heightCalculated = 0.0

  var body: some View {

    ScrollView {
      ForEach(0..<10) { i in
        Text("\(i)")
      }
      .background(
        GeometryReader { proxy in
          Color.clear.onAppear { 
            heightCalculated = proxy.size.height
            debugPrint(proxy.size.height)
          }
        }
      )

    }
    .frame(height: heightCalculated)
  }

@byJeevan
Copy link
Author

To make Content view as parameter. A better way to build Generic views.

struct ContainerView<Content: View>: View {
  @ViewBuilder var content: Content
    
  var body: some View {
    content
  }
}


// Usage
ContainerView{
  ...
}

@byJeevan
Copy link
Author

Debugging:

  1. let _ = Self._printChanges() print statement to trace view lifecycle
  2. let _ = print("Update XYZView") We use this print to trace whether the body of Particular view (eg. SpyView) is executed or not.

ref: https://sarunw.com/posts/how-to-do-print-debugging-in-swiftui/

@byJeevan
Copy link
Author

To make fullscreen view to fill entire screen:

ZStack {
   Text("Hello").background(.yellow)
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // this is important
.background(.blue)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment