SwiftUI Adaptive Design: Creating Flexible UIs for All Devices

In the ever-evolving world of iOS development, creating adaptive user interfaces is crucial for delivering a seamless experience across various devices and orientations. SwiftUI provides several powerful tools to help developers craft flexible layouts that respond gracefully to different screen sizes and configurations. Let’s explore some key techniques for adaptive design in SwiftUI.

GP Forecast Adaptive Designs

ViewThatFits: Automatic Layout Adaptation

ViewThatFits is a SwiftUI container view that automatically selects the first child view that fits within the available space. This powerful tool simplifies the process of creating adaptive layouts by allowing you to define multiple layout options and letting SwiftUI choose the most appropriate one. Here’s an example of how to use ViewThatFits:

ViewThatFits {
    HStack {
        Image(systemName: "star.fill")
        Text("Favorite")
    }

    Image(systemName: "star.fill")
}

In this example, SwiftUI will display the HStack with both the image and text if there’s enough space. If not, it will fall back to showing just the image.

Size Classes: Adapting to Device Characteristics

Size Classes in SwiftUI allow you to adapt your layout based on the available space and device characteristics. By using the @Environment property wrapper, you can access the current horizontal and vertical size classes to make layout decisions.
Here’s how you can use size classes to adjust your layout:

@Environment(\.horizontalSizeClass) var horizontalSizeClass

var body: some View {
    Group {
        if horizontalSizeClass == .compact {
            VStack {
                // Compact layout
            }
        } else {
            HStack {
                // Regular layout
            }
        }
    }
}

This approach allows you to create distinct layouts for different device configurations, such as iPhones in portrait mode versus iPads in landscape mode. Here you can find all the size classes for each Apple Device.

GeometryReader: Fine-Grained Layout Control

GeometryReader is a powerful tool that provides detailed information about the size and position of its parent view. This allows for precise control over layout and sizing based on the available space. Here’s an example of using GeometryReader to create a responsive layout:

GeometryReader { geometry in
    VStack {
        Text("Hello, World!")
            .font(.system(size: geometry.size.width / 10))

        Rectangle()
            .fill(Color.blue)
            .frame(width: geometry.size.width * 0.8, height: 50)
    }
}

In this example, the text size and rectangle width are dynamically calculated based on the available width, ensuring the layout remains proportional across different screen sizes.

UIScreen.main.bounds.width: A Practice to Avoid

While it might be tempting to use UIScreen.main.bounds.width for layout calculations, this approach is considered bad practice in SwiftUI. It doesn’t account for factors like Split View on iPad, rotation changes, or future device form factors.
Instead of relying on UIScreen.main.bounds.width, it’s better to use SwiftUI’s built-in layout system, including GeometryReader, size classes, and flexible spacing. These tools provide a more robust and future-proof way to create adaptive layouts.

Conclusion

Adaptive design in SwiftUI is about creating flexible, responsive layouts that work well across all iOS devices. By leveraging tools like ViewThatFits, size classes, and GeometryReader, you can create UIs that automatically adjust to different screen sizes and orientations. Remember to avoid hard-coding dimensions or relying on specific device characteristics, and instead embrace SwiftUI’s powerful layout system for truly adaptive designs.

Here is a Login Screen example;

Login Screen example
import SwiftUI

struct LoginView: View {
    @State private var username = ""
    @State private var password = ""
    @State private var rememberMe = false
    @State private var showingAlert = false
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                VStack(spacing: 20) {
                    // Avatar Image
                    Image(systemName: "person.circle.fill")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: horizontalSizeClass == .compact ? geometry.size.width * 0.3 : geometry.size.width * 0.2)
                        .foregroundColor(.blue)
                        .padding(.top, 40)
                    
                    // Welcome Text
                    Text("Welcome Back!")
                        .font(.system(size: geometry.size.width * 0.06))
                        .fontWeight(.bold)
                    
                    // Login Form
                    VStack(spacing: 15) {
                        TextField("Username", text: $username)
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                            .autocapitalization(.none)
                        
                        SecureField("Password", text: $password)
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                        
                        Toggle("Remember Me", isOn: $rememberMe)
                    }
                    .padding(.horizontal)
                    .frame(width: horizontalSizeClass == .compact ? geometry.size.width * 0.5 : geometry.size.width * 0.3)
                    
                    // Login Button
                    Button(action: {
                        // Perform login action
                        showingAlert = true
                    }) {
                        Text("Log In")
                            .fontWeight(.semibold)
                            .foregroundColor(.white)
                            .frame(maxWidth: .infinity)
                            .padding()
                            .background(Color.blue)
                            .cornerRadius(10)
                    }
                    .padding(.horizontal)
                    .frame(width: geometry.size.width * 0.3, height: geometry.size.width * 0.3)
                    
                    // Forgot Password Link
                    Button("Forgot Password?") {
                        // Handle forgot password action
                    }
                    .foregroundColor(.blue)
                    
                    // Social Login Options
                    ViewThatFits {
                        HStack(spacing: 20) {
                            socialLoginButton(imageName: "apple.logo", text: "Sign in with Apple")
                            socialLoginButton(imageName: "g.circle", text: "Sign in with Google")
                        }
                        VStack(spacing: 10) {
                            socialLoginButton(imageName: "apple.logo", text: "Sign in with Apple")
                            socialLoginButton(imageName: "g.circle", text: "Sign in with Google")
                        }
                    }
                    .padding(.top)
                    
                    // Sign Up Link
                    HStack {
                        Text("Don't have an account?")

                        Button("Sign Up") {
                            // Handle sign up action
                        }
                        .foregroundColor(.blue)
                    }
                    .padding(.top)
                }
                .padding()
                .frame(minHeight: geometry.size.height)
            }
        }
        .alert(isPresented: $showingAlert) {
            Alert(title: Text("Login Attempt"), message: Text("Username: \(username)\nPassword: \(password)"), dismissButton: .default(Text("OK")))
        }
    }
    
    private func socialLoginButton(imageName: String, text: String) -> some View {
        Button(action: {
            // Handle social login action
        }) {
            HStack {
                Image(systemName: imageName)

                Text(text)
            }
            .padding()
            .frame(maxWidth: .infinity)
            .background(Color.gray.opacity(0.2))
            .cornerRadius(10)
        }
    }
}


#Preview {
    LoginView()
}

Understanding Xcode Crash Logs and Symbolication

When developing applications, encountering crashes is an inevitable part of the process. However, analyzing crash logs can provide invaluable insights into what went wrong, helping developers fix bugs and improve app stability. This blog post will explore how to access crash logs in Xcode and the process of symbolication, which transforms unreadable crash reports into meaningful information.

Accessing Crash Logs in Xcode

Crash logs are automatically generated when an app crashes on a user’s device. If users have opted to share crash data, these logs can be accessed through Xcode. Here’s how you can find and review these logs:

  • Open Xcode: Launch the application on your Mac.
  • Navigate to Organizer: From the menu bar, select Window > Organizer.
  • Select Crashes: In the Organizer window, click on the Crashes tab. Here, you can view crash reports from your app’s builds distributed via TestFlight or the App Store.
  • View Individual Reports: You can control-click on a specific crash report to open it in Finder for a more detailed examination.
  • Analyze Statistics: The Organizer provides at-a-glance statistics about crashes, such as the percentage of users experiencing issues across different iOS versions and devices.

For more granular analysis, you can export crash reports directly from devices or through the Console app for Mac applications. This allows you to troubleshoot individual incidents that may not be reflected in aggregated data.

Understanding Crash Logs

A typical crash log contains several key pieces of information:

  • Exception Type: Indicates what kind of error occurred (e.g., SIGABRT).
  • Termination Reason: Provides additional context on why the app terminated.
  • Backtrace: A list of function calls leading up to the crash, showing where in your code the issue occurred.

However, these logs are often presented in a raw format that includes memory addresses instead of function names, making them difficult to interpret without additional processing.

The Process of Symbolication

Symbolication is the process of converting these raw memory addresses into human-readable function names, file names, and line numbers. This is crucial for diagnosing crashes effectively.

  • Ensure dSYM Files are Available: When you build your app for distribution (e.g., via TestFlight or App Store), ensure that you include dSYM files. These files contain debug symbols necessary for symbolication.
  • Use Xcode’s Symbolication Tools:
  • Open Xcode and navigate to the Crashes section in the Organizer.
  • Select a crash report to view its details.
  • If your dSYM files are correctly linked, Xcode will automatically symbolicate the crash log, allowing you to see exactly where in your code the crash occurred.
  • Manual Symbolication: If you have a raw crash log file (e.g., .ips or .xccrashpoint), you can manually symbolicate it using Terminal commands or third-party tools like atos. For example:
atos -o /path/to/your/app.dSYM/Contents/Resources/DWARF/YourApp -arch arm64 -l <load_address> <address>

Replace <load_address> and <address> with values from your crash log.

Best Practices for Handling Crash Logs

  • Prioritize Crashes: Not all crashes affect users equally; prioritize fixing issues that impact a larger user base.
  • Integrate Analytics Tools: Consider using third-party tools that provide enhanced analytics and visualization for crash reports.
  • Regularly Review Logs: Make it a habit to review crash logs after each release or major update to catch any new issues early.

Conclusion

Understanding and managing crash logs is a vital skill for any iOS developer. By effectively accessing and symbolizing these logs in Xcode, developers can gain critical insights into their applications’ performance and stability. This not only helps in resolving issues but also enhances user experience by ensuring that apps run smoothly. With regular practice and familiarity with these tools, developers can significantly improve their debugging processes and overall application quality.

Unlocking Productivity: The Ultimate Guide to Xcode Keyboard Shortcuts

In the world of iOS and macOS development, Xcode is the powerhouse tool that developers rely on daily. While Xcode is packed with features, mastering its keyboard shortcuts can significantly boost your productivity. Here’s a comprehensive guide to some of the best Xcode shortcuts, along with a nifty trick on how to customize them to fit your workflow.

Essential Xcode Shortcuts
Navigation:
Cmd + Shift + O: Open Quickly – This is your go-to for opening any file in your project instantly.
Cmd + 0: Show/Hide the Navigator – Toggle the left sidebar on and off.
Cmd + 1 to Cmd + 9: Switch between different Navigator sections (Project, Source Control, Debug, etc.).

Editing:
Cmd + /: Comment/Uncomment the selected line or block of code.
Cmd + D: Duplicate the current line or selection.
Control + /: Indent or un-indent the current line or selection.
Option + Delete: Delete the word to the left of the cursor.

Code Completion and Navigation:
Esc: Complete the current word or show code completion suggestions.
Cmd + Click: Jump to the definition of a symbol or open the Quick Help for it.
Control + 6: Jump to the previous issue or warning in the Issue Navigator.

Debugging:
Cmd + Y: Show or hide the Debug Area.
Cmd + Shift + Y: Show or hide the Debug Area and the Navigator at the same time.
Cmd + Option + I: Show the Debug Navigator.

Customizing Your Shortcuts
One of the lesser-known features of Xcode is its ability to customize keyboard shortcuts. This can be particularly useful if you find yourself frequently performing a specific action or if you’re switching from another IDE with different shortcuts.

How to Create Custom Shortcuts:
Open Preferences: Go to Xcode > Preferences > Key Bindings.
Find Your Command: Use the search bar at the top to find the command you want to customize. For instance, if you want to delete the current line, you might search for “Delete to End of Line” or “Delete Line”.
Assign a New Shortcut: Click in the field next to the command and press your desired key combination. For example, Shift + Control + Option + Command + L could be set to delete the current line, as you’ve done.

Tips for Customization:
Avoid Conflicts: Make sure your custom shortcut doesn’t conflict with existing Xcode or system shortcuts.
Consistency: Try to keep your shortcuts consistent with other tools you use to avoid confusion.
Document Your Changes: Keep a note of your custom shortcuts, especially if they’re complex, to save time in remembering them.

Conclusion
Mastering Xcode’s keyboard shortcuts is like learning a new language that speaks directly to your productivity. By integrating these shortcuts into your daily workflow, you’ll find that tasks that once took several clicks now take just a moment. And by customizing your shortcuts, you tailor Xcode to your unique coding style, making your development environment not just a tool, but an extension of your thought process. Whether you’re a seasoned developer or just starting out, these shortcuts will help you code smarter, not harder. Happy Xcoding!

Xcode Tip: Mastering LLDB debugger

Mastering Xcode Debugging with LLDB: A Step-by-Step Guide to Changing Variables at Breakpoints

In the realm of iOS and macOS development, Xcode stands as the primary Integrated Development Environment (IDE), offering powerful tools for developers, including the LLDB debugger. LLDB, or LLVM Debugger, is a powerful successor to GDB, providing advanced debugging capabilities right within Xcode. One of the lesser-known but incredibly useful features of LLDB is the ability to modify variable values directly during a debugging session. Here’s a comprehensive guide on how to leverage this feature to enhance your debugging workflow.

Understanding LLDB in Xcode

LLDB is more than just a debugger; it’s an interactive environment where you can inspect and manipulate your program’s state. When your app hits a breakpoint, you’re not just pausing execution; you’re entering a session where you can query, modify, and even execute code within the context of your app’s current state.

Setting Up Your Debugging Session

  1. Open Your Project in Xcode: Launch Xcode and open your project.
  2. Set a Breakpoint: Navigate to the line of code where you want to pause execution. Click in the gutter to the left of the line number to set a breakpoint.
  3. Run Your App in Debug Mode: Click the run button or use the shortcut (Cmd + R). Your app will run until it hits the breakpoint.

Modifying Variables with LLDB

Once your app hits the breakpoint, follow these steps:

  1. Open the Debugger Console: At the bottom of Xcode, there’s a console area. If it’s not visible, go to View > Debug Area > Activate Console or use the shortcut (Cmd + Shift + Y).
  2. Inspect Variables: You can see local variables in the debugger sidebar. Alternatively, type frame variable in the console to list variables in the current scope.
  3. Change a Variable’s Value:
  • Direct Assignment: If you want to change a variable named myVariable to 10, simply type:
    expr myVariable = 10
  • Using Expressions: You can also use more complex expressions. For instance, to increment a counter:
    expr counter++
  1. Confirm Changes: After modifying a variable, you can check its new value by typing frame variable again or by hovering over the variable in your code if you’ve enabled variable watching.

Practical Example

Imagine you’re debugging a function that calculates the factorial of a number, and you want to see how changing the input affects the result:

func factorial(_ n: Int) -> Int {
    if n <= 1 {
        return 1
    } else {
        return n * factorial(n - 1)
    }
}
  • Set a breakpoint inside the factorial function.
  • Run your app to hit the breakpoint.
  • Change the value of n:
  expr n = 5
  • Continue execution (Cmd + Option + Y) to see how this change affects the function’s output.

Tips for Effective Debugging with LLDB

  • Use help in the console to discover more commands.
  • Watch Expressions: You can add expressions to watch in the debugger sidebar, which updates dynamically as you modify values.
  • Breakpoints with Conditions: Set breakpoints that only trigger when certain conditions are met, reducing unnecessary pauses.

Conclusion

Mastering LLDB within Xcode can significantly boost your productivity as a developer. The ability to alter variable values at runtime not only aids in debugging but also in understanding the flow and impact of your code in real-time. This technique is particularly useful for experimenting with different scenarios without needing to recompile or rerun your application. Remember, effective debugging is about exploring, understanding, and sometimes, creatively manipulating your code’s environment to uncover issues or test hypotheses. Happy debugging!

Xcode tip: Reveal Editor History

In Xcode in the editor window you can use CONTROL+CMD+ Arrow left/right, to go forward and backwards in files which you have had in your editor. This is really nice and effective to go one or two files back. But if you want to go back many files you can tap and hold on the < and > buttons to reveal the complete history.

Xcode SwiftUI Preview Tips

Loved by some, hated by many developers — is the SwiftUI preview. I was also in the latter camp to be honest, until recently.

Here are some things you can do to enjoy your SwiftUI previews again.

When you get an error, tap on the error and read it carefully, often it gives a hint. For example, you have been changing code left and right, then you went to this certain view, and you activated the preview pane with Option+Command+Enter — and getting an error.

Make sure before activating a preview canvas, that your code builds and if not, then fix the error and try again.

If you have done this and you still get an error, make sure you don’t have another tab open with a preview active for another target, close those and try again.

If that also fails you can try to clear the preview cache with this terminal command, I made an alias in .zprofile called simprevdel.

Compiler flag PREVIEW

You can add the PREVIEW compiler flag to use it as a compiler directive to load preview mock data.

Don’t forget to inject the EnvironmentObject model in the Preview View.

Enjoy your previews!

Automate your Xcode workflow / Dev tools with keyboard shortcuts

Hardware

As developers, we start and switch a lot between common developer applications each day. You can buy some external hardware keyboards to achieve this, like for example with these USB Mini 3-Key Keypad, or the HUION Keydial, 9-Keys USB Mini Keypad, Programming Macro Pad. They work fine but you must always pack them in your bag when going to your workspace, but make sure before purchasing them, that they have macOS software drivers.

macOS Shortcuts

Personally I have the Huion, and use it for general Xcode keyboard shortcuts — like show / hide panes, delete current line, Run, Stop, etc, and to save keys I use macOS Shortcuts to launch applications like Xcode.

To achieve this do the following:

  1. open Shortcuts
  2. tap on the + in the title bar
  3. In the categories tab on the right, select Open App, and drag it on the canvas
  4. Click on App, to select Xcode
  5. Click on the info circle top right > Add keyboard shortcut > Control+Option+Command+x
  6. If you see on the canvas, Receive input from, deselect all
  7. Tap on the default name in the title: Open App, to change it to Open Xcode, and you can optionally assign an icon, but this is not really needed.
  8. Now comes the confusing part, there is no save, you just close this window with the red x
  9. Close Shortcuts
  10. Test by tapping Control+Option+Command+x

Other Dev tools

Now you can repeat this process for your most common developer tools like SourceTree, Interactful, Slack, enjoy!

SwiftUI autocomplete tricks

Fonts

There are some view properties which you use a lot and are difficult to type like:

.font(.caption)

here you type .font [ENTER] -> this makes .font(), you have to cursor back into the brackets to type .cap[ENTER]

This you can achieve faster by typing .fontcaption[ENTER], or .fonttitle[ENTER]

Frames

You probably know already the properties trick to type the first two characters of the properties to get them preselected like so typing .framewihe[ENTER]:

If you only want one of those sub properties you can type directly

.width[ENTER]

Xcode tip: Fix misplaced compiler directives like #if os(iOS)

When developing for multiple Apple platforms you can use compiler directives to customize properties for each OS, so you can use one View for multiple platforms. However Xcode aligns them in the code but not at the beginning of each line. There is a simple trick to auto align them all at once in a View by deleting the closing bracket of the View and adding it back.

Before delete:

After delete/adding of last bracket at line 128.