-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Fix tab content view recreation #24
base: release/2.0.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,32 +25,31 @@ import SwiftUI | |
import TabBar | ||
|
||
struct ContentView: View { | ||
|
||
private enum Item: Int, Tabbable { | ||
case first = 0 | ||
case second | ||
case third | ||
|
||
var icon: String { | ||
switch self { | ||
case .first: return "house" | ||
case .second: return "magnifyingglass" | ||
case .third: return "person" | ||
case .first: "house" | ||
case .second: "magnifyingglass" | ||
case .third: "person" | ||
} | ||
} | ||
|
||
var title: String { | ||
switch self { | ||
case .first: return "First" | ||
case .second: return "Second" | ||
case .third: return "Third" | ||
case .first: "First" | ||
case .second: "Second" | ||
case .third: "Third" | ||
} | ||
} | ||
} | ||
|
||
@State private var selection: Item = .first | ||
@State private var visibility: TabBarVisibility = .visible | ||
|
||
var body: some View { | ||
TabBar(selection: $selection, visibility: $visibility) { | ||
Button { | ||
|
@@ -61,15 +60,32 @@ struct ContentView: View { | |
Text("Hide/Show TabBar") | ||
} | ||
.tabItem(for: Item.first) | ||
Text("Second") | ||
|
||
TextWrapper() | ||
.tabItem(for: Item.second) | ||
Text("Third") | ||
|
||
TextWrapper() | ||
.tabItem(for: Item.third) | ||
} | ||
.tabBar(style: CustomTabBarStyle()) | ||
.tabItem(style: CustomTabItemStyle()) | ||
.onChange(of: selection) { newValue in | ||
print("selection changed:", newValue) | ||
} | ||
} | ||
} | ||
|
||
struct TextWrapper: View { | ||
@State var string: String = UUID().uuidString | ||
|
||
var body: some View { | ||
Text(string) | ||
.onTapGesture { | ||
string = UUID().uuidString | ||
} | ||
.onAppear(perform: { | ||
print("onAppear:", string) | ||
}) | ||
} | ||
} | ||
Comment on lines
+78
to
90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please clarify the purpose of this wrapper? I didn't quite understand it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for the purpose of testing recreation and lazy loading, no other meaning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it will be better revert these changes, as Example project most likely will be updated later. |
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,19 @@ | ||
// swift-tools-version:5.2 | ||
// swift-tools-version:5.3 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "TabBar", | ||
platforms: [ | ||
.iOS(.v13) | ||
.iOS(.v14), | ||
winddpan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
], | ||
products: [ | ||
.library( | ||
name: "TabBar", | ||
targets: ["TabBar"] | ||
) | ||
), | ||
], | ||
targets: [ | ||
.target(name: "TabBar") | ||
.target(name: "TabBar"), | ||
] | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,135 +25,130 @@ import SwiftUI | |
|
||
/** | ||
`TabBar` – highly customizable tab bar for your SwiftUI application. | ||
|
||
By using this component you will be able to add a view that | ||
switches between multiple child views using interactive user | ||
interface elements. | ||
|
||
`TabBar` can be easily customized. You have to conform | ||
to `TabBarStyle` and `TabItemStyle` to customize bar | ||
and item respectively. To apply customization you have to inject | ||
them to tab bar using `tabBar(style:)` for bar | ||
and `tabItem(style:)` for item. | ||
|
||
Usage: | ||
|
||
``` | ||
TabBar(selection: $selection) { } | ||
.tabBar(style: CustomTabBarStyle()) | ||
.tabItem(style: CustomTabItemStyle()) | ||
``` | ||
*/ | ||
public struct TabBar<TabItem: Tabbable, Content: View>: View { | ||
|
||
private let selectedItem: TabBarSelection<TabItem> | ||
private let content: Content | ||
|
||
private var tabItemStyle : AnyTabItemStyle | ||
private var tabBarStyle : AnyTabBarStyle | ||
|
||
@State private var items: [TabItem] | ||
|
||
@StateObject private var selectedItem: TabBarSelection<TabItem> | ||
private let content: () -> Content | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing the closure as a parameter rather than invoking it immediately could lead to performance issues, as it disrupts the memoization process of SwiftUI. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact, delayed closures perform better in practice. SwiftUI needs to construct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing closures in a parameter makes the |
||
|
||
private var tabItemStyle: AnyTabItemStyle | ||
private var tabBarStyle: AnyTabBarStyle | ||
|
||
@Binding private var visibility: TabBarVisibility | ||
|
||
/** | ||
Creates a tab bar components with given | ||
bindings to selection and visibility. | ||
|
||
Provided views in the `content` closure | ||
will be recognized as a tab bar item only | ||
if they have `tabItem(for:)` applied on them. | ||
*/ | ||
public init( | ||
selection: Binding<TabItem>, | ||
visibility: Binding<TabBarVisibility> = .constant(.visible), | ||
@ViewBuilder content: () -> Content | ||
@ViewBuilder content: @escaping () -> Content | ||
) { | ||
self.tabItemStyle = .init(itemStyle: DefaultTabItemStyle()) | ||
self.tabBarStyle = .init(barStyle: DefaultTabBarStyle()) | ||
|
||
self.selectedItem = .init(selection: selection) | ||
self.content = content() | ||
|
||
self._items = .init(initialValue: .init()) | ||
self._visibility = visibility | ||
tabItemStyle = .init(itemStyle: DefaultTabItemStyle()) | ||
tabBarStyle = .init(barStyle: DefaultTabBarStyle()) | ||
|
||
_selectedItem = .init(wrappedValue: .init(selection: selection)) | ||
self.content = content | ||
_visibility = visibility | ||
} | ||
|
||
private var tabItems: some View { | ||
HStack { | ||
ForEach(self.items, id: \.self) { item in | ||
self.tabItemStyle.tabItem( | ||
ForEach(selectedItem.items, id: \.self) { item in | ||
tabItemStyle.tabItem( | ||
icon: item.icon, | ||
selectedIcon: item.selectedIcon, | ||
title: item.title, | ||
isSelected: self.selectedItem.selection == item | ||
isSelected: selectedItem.selection == item | ||
) | ||
.onTapGesture { | ||
self.selectedItem.selection = item | ||
self.selectedItem.objectWillChange.send() | ||
selectedItem.selection = item | ||
} | ||
} | ||
.frame(maxWidth: .infinity) | ||
} | ||
} | ||
|
||
public var body: some View { | ||
ZStack { | ||
self.content | ||
content() | ||
.frame(maxWidth: .infinity, maxHeight: .infinity) | ||
.environmentObject(self.selectedItem) | ||
.environmentObject(selectedItem) | ||
|
||
GeometryReader { geometry in | ||
VStack { | ||
Spacer() | ||
self.tabBarStyle.tabBar(with: geometry) { | ||
.init(self.tabItems) | ||
|
||
tabBarStyle.tabBar(with: geometry) { | ||
.init(tabItems) | ||
} | ||
} | ||
.edgesIgnoringSafeArea(.bottom) | ||
.visibility(self.visibility) | ||
.visibility(visibility) | ||
} | ||
} | ||
.onPreferenceChange(TabBarPreferenceKey.self) { value in | ||
self.items = value | ||
if value != selectedItem.items { | ||
selectedItem.items = value | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
extension TabBar { | ||
public extension TabBar { | ||
/** | ||
A function that is used to apply tab item's style on `TabBar`. | ||
|
||
By passing the instance of object that conforms to `TabItemStyle` | ||
protocol `TabBar` will use this style for its items. | ||
|
||
- Parameters: | ||
- style: Item style that should be applied to `TabBar`. | ||
|
||
- Returns: | ||
`TabBar` with applied item style. | ||
*/ | ||
public func tabItem<ItemStyle: TabItemStyle>(style: ItemStyle) -> Self { | ||
func tabItem(style: some TabItemStyle) -> Self { | ||
var _self = self | ||
_self.tabItemStyle = .init(itemStyle: style) | ||
return _self | ||
} | ||
|
||
/** | ||
A function that is used to apply tab bar's style on `TabBar`. | ||
|
||
By passing the instance of object that conforms to `TabBarStyle` | ||
protocol `TabBar` will apply this style to its bar. | ||
|
||
- Parameters: | ||
- style: Bar style that should be applied to `TabBar`. | ||
|
||
- Returns: | ||
`TabBar` with applied bar style. | ||
*/ | ||
public func tabBar<BarStyle: TabBarStyle>(style: BarStyle) -> Self { | ||
func tabBar(style: some TabBarStyle) -> Self { | ||
var _self = self | ||
_self.tabBarStyle = .init(barStyle: style) | ||
return _self | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should remove this
print
statement, as it doesn't add any significant value here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for the purpose of testing, can remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, remove it please.