Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
# Change Log
All notable changes to this project will be documented in this file.

## 3.0.0

### Breaking Changes

* Minimum deployment target raised to iOS 18. iOS 17 is no longer supported.
* `HeaderContext` converted from a struct to an `@Observable` class to address performance issues related to SwiftData queries. The observable class shields content views from re-performing queries and re-evaluating `body` on every frame while scrolling. If you stored `HeaderContext` (or its typealiases `MaterialTabsHeaderContext` / `StickyHeaderContext`) by value, you will need to update to reference semantics.
* #19 `MaterialTabsScroll` joint scroll position API replaced with iOS 18 `ScrollPosition`. The old `scrollAnchor` parameter is replaced by optional `scrollPosition` and `anchor` bindings for joint scroll position manipulation between the library and client code.

### Improvements

* #25 `MaterialTabBar` now supports an `alignment` parameter (`.leading`, `.center`, `.trailing`) for controlling horizontal positioning of self-sized tabs when `fillAvailableSpace` is `false`.
* #25 New `MaterialAccessoryTabBar` component for adding optional leading and trailing accessory views alongside tab selectors. Accessories scroll horizontally with the tabs.
* #25 `TabBarModel` and `HeaderModel` are now public, enabling fully custom tab bar implementations via the environment.

## 2.0.7

### Improvements

* Add context-free initializers to `MaterialTabsScroll` and `StickyHeaderScroll`. The existing initializers pass a `MaterialTabsScrollContext` (or `StickyHeaderScrollContext`) to the content view builder, which includes the content offset. Because the content offset changes on every frame during scrolling, this causes the content view `body` to be re-evaluated continuously. The new initializers omit the context, allowing the content to be shielded from these unnecessary re-evaluations via an internal `ContentWrapperView` with `Equatable` conformance. If your content does not need the context, prefer the new initializers for better scroll performance.

## 2.0.6

### Fixes
Expand Down
58 changes: 48 additions & 10 deletions Demo/Demo/Preview Content/TestMaterialTabsScroll.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ struct TestMaterialTabsScroll: View {
// MARK: - Variables

@State private var selectedTab = 0
@State private var scrollItem: Int?
@State private var scrollUnitPoint: UnitPoint = .top
@State private var scrollPosition = ScrollPosition(idType: Int.self)
@State private var scrollAnchor: UnitPoint? = .top

// MARK: - Body

Expand All @@ -30,28 +30,52 @@ struct TestMaterialTabsScroll: View {
Text("Title").frame(height: titleHeight)
},
headerTabBar: { context in
Text("Tab Bar").frame(height: tabBarHeight)
HStack(spacing: 0) {
ForEach(0..<2) { tab in
Button {
selectedTab = tab
} label: {
Text("Tab \(tab)")
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(selectedTab == tab ? Color.blue.opacity(0.2) : Color.clear)
}
}
}
.frame(height: tabBarHeight)
},
headerBackground: { _ in
Color.yellow.opacity(0.25)
}
) {
MaterialTabsScroll(
tab: 0,
reservedItem: -1,
scrollItem: $scrollItem,
scrollUnitPoint: $scrollUnitPoint
scrollPosition: $scrollPosition,
anchor: $scrollAnchor
) { _ in
LazyVStack(spacing: 0) {
ForEach(0..<25) { index in
VStack(spacing: 0) {
Rectangle().fill(.black.opacity(0.2)).frame(height: 1)
Spacer()
Button("Tap Row \(index)") {
scrollUnitPoint = .top
scrollItem = index
HStack {
Text("Row \(index)")
Spacer()
Button("Top") {
withAnimation {
scrollAnchor = .top
scrollPosition.scrollTo(id: index, anchor: .top)
}
}
Button("Bottom") {
withAnimation {
scrollAnchor = .bottom
scrollPosition.scrollTo(id: index, anchor: .bottom)
}
}
}
.buttonStyle(.bordered)
.padding(.horizontal)
Spacer()
}
.frame(height: rowHeight)
Expand All @@ -60,8 +84,22 @@ struct TestMaterialTabsScroll: View {
}
.scrollTargetLayout()
}
.materialTabItem(tab: 0, label: .secondary("Tab 0"))
MaterialTabsScroll(tab: 1) { _ in
LazyVStack(spacing: 0) {
ForEach(0..<25) { index in
VStack(spacing: 0) {
Rectangle().fill(.black.opacity(0.2)).frame(height: 1)
Spacer()
Text("Tab 1 — Row \(index)")
Spacer()
}
.frame(height: rowHeight)
}
}
}
.materialTabItem(tab: 1, label: .secondary("Tab 1"))
}
.animation(.default, value: scrollItem)
}
}

Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "SwiftUIMaterialTabs",
platforms: [.iOS(.v17)],
platforms: [.iOS(.v18)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
Expand Down
Loading