In the safari or chrome app, when I want to change tabs, I can go into a grid view of the tabs. In this grid, each tab shows the content of the page. When I click on one of the tabs, the content of the page expands to fill the entire screen (and shrinks when I go back to grid).
I'm creating my own browser and I'm trying to replicate this same functionality. I'm using WebKit on XCode 16.4, iOS 18. However, I'm unable to figure out how Chrome and Safari did this.
First, I thought that I could take a snapshot of the page and then use that image as the thumbnail. However, very often the image is of the wrong size - likely due to the webview shrinking for the animation.
Making the animation wait until the image is made available did help in making it more consistent. The above errors happen whenever I spam the new tab and then click the tab grid button. It only is misaligned on the very last new tab. Please help on this.
// OpenedTab.swift
Button(action: {
tab.getThumbnail {
tabManager.selectedTab = nil
}
}) {
ZStack {
Image(systemName: "square")
.resizable()
.frame(width: 25, height: 25)
Text(tabManager.tabs.count.description)
.font(.subheadline)
}
}
// TabState.swift
func getThumbnail(completionHandler: (() -> Void)? = nil) {
webView.takeSnapshot(with: nil) { img, err in
if let err = err {
print("Snapshot err: \(err)")
} else {
self.thumbnail = img
completionHandler?()
}
}
}
Also, something I'm noticing is that for some reason, the image is slightly bigger than the header of the tab card. It also happens in the progress view if the thumbnail isn't available. The images above show it too.
I have no clue why this is happening and I would love advice on this too.
struct TabCardView: View {
@StateObject var manager = TabManager.shared
@ObservedObject var tab: TabState
var namespace: Namespace.ID
@State var width: CGFloat = 0
var body: some View {
GeometryReader { geo in
VStack(spacing: 0) {
HStack(spacing: 1) {
Text(tab.title ?? tab.url.host() ?? "")
.font(.caption2)
.padding(.horizontal, 4)
.padding(.vertical, 10)
Button(action: {
manager.close(tab: tab)
}) {
Image(systemName: "multiply")
}
}
.frame(height: 40)
.frame(width: geo.size.width)
// .padding(.horizontal, 7)
.background(.tertiary)
.matchedGeometryEffect(id: tab.id.uuidString + "title", in: namespace)
ZStack {
if let thumbnail = tab.thumbnail {
Image(uiImage: thumbnail)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width, height: 160, alignment: .top)
.clipped()
} else {
Color.black.brightness(0.8)
ProgressView()
}
}
.frame(width: geo.size.width, height: 160)
.matchedGeometryEffect(id: tab.id.uuidString + "container", in: namespace)
}
.frame(width: geo.size.width)
}
.frame(height: 200)
.clipShape(RoundedRectangle(cornerRadius: 16))
.shadow(radius: 2)
.padding(.all, 7)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(.blue, lineWidth: manager.previousTab?.id == tab.id ? 5 : 0)
)
.shadow(radius: 1)
}
}