How I Handle External Links in My iOS App with a Built-In Copy Option

How I Handle External Links in My iOS App with a Built-In Copy Option

Have you ever tapped an email link and had it open in the wrong app and wished you could've simply copied it instead? I did. And so for my apps I created a simple reusable SwiftUI view that lets the person tapping the link within my iOS and macOS apps have the power to decide whether to open or copy any external link, whether it's a website or a mailto URL.

Pinterest geared image showing my post title, images from below, and my main URL.

Introduction

When I first started building Simply Customize It I wanted to make sure that anytime a user encountered an external link, whether it was for a website or for a support-request email, they could easily choose to skip opening it and instead copy it.

I’ve run into issues in the past where tapping a mail link would open the default Mail app instead of my preferred primary email like Gmail or Spark. I'd then have to copy and paste the address manually.... sometimes even having to clean it up first. Through this I realized that people using my app might want to preview the link first or just open it in a completely different context.

As such I built a simple SwiftUI view with a built-in copy button next to the link. And recently, when I saw a Reddit thread asking how to handle this more flexibly I figured it was time to share my version and how it works.

This solution works for email, websites, or any other URL while offering a clean, flexible experience across both iOS and macOS.


What It Looks Like in My App

I use this setup in several of my apps (Simply Customize It, Simply Match It, and Simply Uncover It) mostly in the About and/or Support sections. It’s a reusable, streamlined view that displays:

  • An overwritable caption (for example "Email Me" or "Visit Site")
  • right arrow to indicate it’s tappable
  • copy icon to the left that lets you grab the URL without opening the link

The copy button is helpful as it allows:

  • Use of an alternate app (for example Gmail instead of Mail)
  • Preview ability of where the link leads
  • Copying the address to paste later or share in another way

Here’s how it appears in context:

Image shows a collage of Simply Customize it and Simply Uncover It. Both show the about page festooned with these links while on the far left the link is used on the subscription page.
Here's how it looks on the mac...
Image shows a collage of Simply Customize it, Simply Match It, and Simply Uncover It on the iPhone. All show the about page festooned with these links while on the far left the link is used on the subscription page.
... and here's how it looks on the iPhone.

The Code

I’ve included below my reusable SwiftUI view, DisplayLinkAndCopy, which combines a tappable Link(destination:) with a handy copy button for grabbing the link (or any custom string) to your clipboard. The code in its entirety is available on GitHub here but I'll break it down per view below.

SimplyKyraBlog/SwiftExamples/DisplayLinkAndCopyViews.swift at main · SimplyKyra/SimplyKyraBlog
Bits of code that I’m sharing with the world to hopefully make your life a little easier! - SimplyKyra/SimplyKyraBlog

The code in its entirety is available on GitHub here.

Image shows a collage of three views in the Swift Playground.
Here's the main wrapper, copy button, and link views. All shown below with copy-able code tested, again, in Swift Playgrounds before being added here and to the GitHub file.
Image shows the ContentView code shown in the GitHub file with an arrow pointing to the view the results in the tutorial code.
Using the code you will get this view with three example links on it making it simple to know how to plug and play in your own code.

Main Wrapper View

This view wraps both the copy button and the display link. You can use it as-is or rename it to fit your own codebase. You can call DisplayLinkAndCopy with just the URL while also allowing flexibility with the:

  • copyString letting you define something else that's copied to the clipboard allowing you to leave off the mailto: in email URLs
  • captionString overwrite the URL string with a more readable caption
import SwiftUI

public struct DisplayLinkAndCopy: View {
    public var url: URL
    
    // Below values optional as they both default to url.absoluteString
    public var copyString: String?
    public var captionString: String?
    
    public var body: some View {
        HStack {
            CopyStringButton(myString: copyString ?? url.absoluteString)
                .padding(.horizontal, 10)
            Spacer()
            DisplayLink(URLsource: url, captionText: captionString ?? url.absoluteString)
        }.padding(.horizontal, 20)
    }
}

The Copy Button

This view handles the actual clipboard logic using UIPasteboard on iOS and NSPasteboard on macOS. By default, it displays a simple copy icon doc.on.doc but you can easily swap this out. I’ve included a commented-out alternative using a Label which you can uncomment or adapt if you’d prefer a visible text label alongside the icon. You could even pass in a custom string if you’d like to make the button more descriptive or accessible.

import SwiftUI

public struct CopyStringButton: View {
    public var myString: String
    
    public init(myString: String) {
        self.myString = myString
    }
    
    public var body: some View {
        Button(action: {
#if os(macOS)
            let pasteboard = NSPasteboard.general
            pasteboard.declareTypes([NSPasteboard.PasteboardType.string], owner: nil)
            pasteboard.setString(myString, forType: NSPasteboard.PasteboardType.string)
#else
            UIPasteboard.general.string = myString
#endif
        }, label: {
            Image(systemName: "doc.on.doc")
            // Above is just the icon. If you prefer you can use a label with/without the label hidden:
            // Label("Copy", systemImage: "doc.on.doc").labelsHidden()
        })
    }
}

This view displays the tappable caption using Link(destination:) and includes a chevron icon to visually indicate it’s clickable by giving it a similar look to what you’d expect in a NavigationLink. If for some reason, like bypassing the wrapper view, a URL isn't passed in a message is gracefully shown rather than crashing the app.

import SwiftUI

public struct DisplayLink: View {
    public var URLsource:URL?
    public var captionText:String
    
    public init(URLsource: URL? = nil, 
                captionText: String? = nil) {
        self.URLsource = URLsource
        self.captionText = captionText ?? "External Link"
    }
    
    public var body: some View {
        if URLsource != nil {
            Link(destination: URLsource!) {
                HStack {
                    Spacer()
                    Text(captionText)
                        .multilineTextAlignment(.center)
                    Spacer()
                    Image(systemName: "chevron.right")
                        .foregroundColor(.secondary)
                    Spacer()
                }
            }
        } else {
            HStack {
                Spacer()
                Text("No URL set for \"\(captionText)\"")
                    .multilineTextAlignment(.center)
                Spacer()
            }
        }
    }
}

How to Use It

You can drop DisplayLinkAndCopy into any SwiftUI view. Here is an example ContentView with a few examples that work in both iOS and macOS:

import SwiftUI

struct ContentView: View {
    let websiteString = "https://www.simplykyra.com/blog/how-i-handle-external-links-in-my-ios-app-with-a-built-in-copy-option"
    let emailString = "mail@simplykyra.com?subject=Display Link Blog Post"
    
    var body: some View {
        VStack(alignment: .center, spacing: 10, content: {
            Text("Here are the examples using both a website and an email.")
            
            Divider()
            
            Text("You can set just the url and by default both caption and the copy value will be the same.")
            
            DisplayLinkAndCopy(
                url: URL(string: websiteString)!
            )
            
            Text("Or overwrite the caption with your own text:")
            
            DisplayLinkAndCopy(
                url: URL(string: websiteString)!,
                captionString: "Click link for blog post!"
            )
            
            Text("Email might be more complicated as the action url includes \"mailto:\" and optional add-ons like the subject so you may want to overwrite the copy text like this:")
            
            DisplayLinkAndCopy(
                url: URL(string: "mailto:\(emailString)")!,
                copyString: emailString,
                captionString: "Mail Me Here!"
            )
            
        }) 
        .multilineTextAlignment(.center)
    }
}

You can drop this into any SwiftUI view with no extra setup needed and have it work for both iOS and macOS. I love that the customizable captionString and copyString values makes this view work for emails, deep links, or complex URLs.


Optional Customizations

Want to tweak it? Here are some quick ideas:

  • Change the icon or add a Label() for accessibility
  • Show a confirmation toast or alert after it copies the string
  • Add haptic feedback (on iOS)
  • Make the copy button optional or revealed on long press

If you do end up customizing this I’d love to see what you come up with so feel free to reach out! You can comment below or send out an email! If you want to follow and comment the links to all of my socials are in this website's footer so check it out below!


Final Thoughts

This small reusable view gives me an easy way to share external links without worrying about where they’ll open and where they're expected to open to. It lets your app's audience choose what works for them while working smoothly across iOS and macOS in SwiftUI.

If you'd rather see the code in one entire file you can grab it from GitHub repo right here. If you do end up using this or building something even better I’d love to hear about it!

Have a variation you want to share? Leave a comment, tag me, or reach out via email.

Hope you’re having a great day and happy building!

And don't forget if you want more posts like this I regularly share new ones to Facebook and Instagram or you can join my email list under the search bar or at the bottom of this post.




Related Posts

Latest Posts