我是 SwiftUI 新手,已经完成了 TabBar UI 的一半。但是,我坚持在中心按钮的两侧添加一条小曲线,类似于示例图像。
我不确定如何在中心按钮的两侧添加小曲线?这是我迄今为止所取得的成就:
下面是我的 TabBar 代码:
import SwiftUI
struct ContentView: View {
// MARK: - HIDING NATIVE TAB BAR
init(){
UITabBar.appearance().isHidden = true
}
var body: some View {
VStack {
Spacer()
TabBarShape()
.fill(Color.white)
.frame(height: 80)
.shadow(color: Color.black.opacity(0.4), radius: 2, x: 0, y: -1)
.overlay(
ZStack {
Button(action: {
print("Create Button Action")
}, label: {
Image("plus_icon")
.frame(width: 60, height: 60, alignment: .center)
.background(Color.custom64B054Color)
.cornerRadius(30)
}).offset(x: 0, y: -36)
Text("Create")
.padding(.top, 32)
HStack(spacing: 0) {
TabBarItem(iconName: "house.fill", action: {})
TabBarItem(iconName: "person.fill", action: {})
}
.frame(height: 80)
}
)
}.ignoresSafeArea()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// MARK: - TabBar Shape
struct TabBarShape: Shape {
// Constants used for the shape
private enum Constants {
static let cornerRadius: CGFloat = 20
static let buttonRadius: CGFloat = 30
static let buttonPadding: CGFloat = 9
}
// Function to define the shape's path
func path(in rect: CGRect) -> Path {
let path = UIBezierPath()
// Move to the starting point at the bottom-left corner
path.move(to: .init(x: 0, y: rect.height))
// Add a line to the upper-left corner, leaving space for the corner radius
path.addLine(to: .init(x: 0, y: rect.height - Constants.cornerRadius))
// Add a quarter-circle in the upper-left corner
path.addArc(withCenter: .init(x: Constants.cornerRadius, y: Constants.cornerRadius),
radius: Constants.cornerRadius,
startAngle: CGFloat.pi,
endAngle: -CGFloat.pi/2,
clockwise: true)
// Calculate the end point for the line before the first button
let lineEnd = rect.width/2 - 2 * Constants.buttonPadding - Constants.buttonRadius
// Add a line to the calculated end point
path.addLine(to: .init(x: lineEnd, y: 0))
// Add a quarter-circle for the first button
path.addArc(withCenter: .init(x: lineEnd, y: Constants.buttonPadding),
radius: Constants.buttonPadding,
startAngle: 0,
endAngle: -CGFloat.pi/2,
clockwise: true)
// Add a half-circle for the first button
path.addArc(withCenter: .init(x: rect.width/2, y: 0),
radius: Constants.buttonPadding + Constants.buttonRadius,
startAngle: 0,
endAngle: CGFloat.pi,
clockwise: false)
// Calculate the start point for the line after the first button
let lineStart = rect.width/2 + 2 * Constants.buttonPadding - Constants.buttonRadius
// Add a quarter-circle for the second button
path.addArc(withCenter: .init(x: lineStart, y: Constants.buttonPadding),
radius: Constants.buttonPadding,
startAngle: -CGFloat.pi/2,
endAngle: -CGFloat.pi/2,
clockwise: true)
// Add a line to the calculated start point for the second button
path.addLine(to: .init(x: rect.width - Constants.cornerRadius, y: 0))
// Add a quarter-circle in the upper-right corner
path.addArc(withCenter: .init(x: rect.width - Constants.cornerRadius, y: Constants.cornerRadius),
radius: Constants.cornerRadius,
startAngle: -CGFloat.pi/2,
endAngle: 0,
clockwise: true)
// Add a line to the bottom-right corner
path.addLine(to: .init(x: rect.width, y: rect.height))
// Close the path to complete the shape
path.close()
// Convert the UIBezierPath to a SwiftUI Path
return Path(path.cgPath)
}
}
// MARK: - TabBar Item
struct TabBarItem: View {
let iconName: String
let action: () -> Void
var body: some View {
Button(action: action) {
Image(systemName: iconName)
.font(.system(size: 24))
.foregroundColor(.blue)
.padding(20)
}
.frame(maxWidth: .infinity)
}
}
如果有人能提供帮助,那就太好了。如果这是我预期的 TabBar UI 的正确方法,有人可以指导我吗?
谢谢您!
我尝试使用与 TabBar Customization in SwiftUI 的答案中使用的构建路径相同的技术来实现此形状(这是我的答案)。
我不熟悉
UIBezierPath
,所以我坚持使用 Path
。
这是修改后的
TabBarShape
:
struct TabBarShape: Shape {
// Constants used for the shape
private enum Constants {
static let cornerRadius: CGFloat = 20
static let smallCornerRadius: CGFloat = 15
static let buttonRadius: CGFloat = 30
static let buttonPadding: CGFloat = 9
}
// Function to define the shape's path
func path(in rect: CGRect) -> Path {
var path = Path()
// Move to the starting point at the bottom-left corner
var x = rect.minX
var y = rect.maxY
path.move(to: CGPoint(x: x, y: y))
// Add the rounded corner on the top-left corner
x += Constants.cornerRadius
y = Constants.buttonRadius + Constants.buttonPadding + Constants.cornerRadius
path.addArc(
center: CGPoint(x: x, y: y),
radius: Constants.cornerRadius,
startAngle: .degrees(180),
endAngle: .degrees(270),
clockwise: false
)
// Add a small corner leading to the main half-circle
x = rect.midX - Constants.buttonRadius - Constants.buttonPadding - Constants.smallCornerRadius
y = Constants.buttonRadius + Constants.buttonPadding - Constants.smallCornerRadius
path.addArc(
center: CGPoint(x: x, y: y),
radius: Constants.smallCornerRadius,
startAngle: .degrees(90),
endAngle: .degrees(30), // 0
clockwise: true
)
// Add the main half-circle
x = rect.midX
y += Constants.smallCornerRadius
path.addArc(
center: CGPoint(x: x, y: y),
radius: Constants.buttonRadius + Constants.buttonPadding,
startAngle: .degrees(215), // 180
endAngle: .degrees(325), // 0
clockwise: false
)
// Add a trailing small corner
x += Constants.buttonRadius + Constants.buttonPadding + Constants.smallCornerRadius
y -= Constants.smallCornerRadius
path.addArc(
center: CGPoint(x: x, y: y),
radius: Constants.smallCornerRadius,
startAngle: .degrees(150), // 180
endAngle: .degrees(90),
clockwise: true
)
// Add the rounded corner on the top-right corner
x = rect.maxX - Constants.cornerRadius
y = Constants.buttonRadius + Constants.buttonPadding + Constants.cornerRadius
path.addArc(
center: CGPoint(x: x, y: y),
radius: Constants.cornerRadius,
startAngle: .degrees(270),
endAngle: .degrees(0),
clockwise: false
)
// Connect the bottom corner
x = rect.maxX
y = rect.maxY
path.addLine(to: CGPoint(x: x, y: y))
// Close the path to complete the shape
path.closeSubpath()
return path
}
}
您会注意到,无需在圆弧之间绘制线条,因为它们是自动添加的。进出主半圆的小角被绘制为单独的圆弧。为了平滑连接,这些弧和主半圆不会一直达到 90 度。这些部分角度是通过一些尝试和错误找到的。
以下是如何在主
body
的改编版本中使用该形状。一些注意事项:
HStack
用于包含常规选项卡栏项目。HStack
添加顶部填充来为按钮保留空间。HStack
下面添加底部填充,以允许插入安全区域。插图使用 GeometryReader
进行测量。var body: some View {
GeometryReader { proxy in
VStack {
Spacer()
HStack(alignment: .bottom) {
TabBarItem(label: "Schedule", iconName: "house.fill") {}
Text("Create")
TabBarItem(label: "Profile", iconName: "person.fill") {}
}
.font(.footnote)
.padding(.top, 50)
.overlay(alignment: .top) {
Button {
print("Create Button Action")
} label: {
Image(systemName: "plus") // "plus_icon"
.resizable()
.scaledToFit()
.padding()
.frame(width: 60, height: 60, alignment: .center)
.foregroundColor(.white)
.background(.green) // custom64B054Color
.cornerRadius(30)
.shadow(radius: 3)
}
.padding(9)
}
.padding(.bottom, max(8, proxy.safeAreaInsets.bottom))
.background {
TabBarShape()
.fill(Color.white)
.shadow(radius: 3)
}
}
.ignoresSafeArea(edges: .bottom)
}
}
我还对
TabBarItem
进行了一些小更改,以包含标签并省略填充:
struct TabBarItem: View {
let label: String
let iconName: String
let action: () -> Void
var body: some View {
Button(action: action) {
VStack(spacing: 4) {
Image(systemName: iconName)
.font(.system(size: 24))
.foregroundColor(.blue)
Text(label)
}
}
.frame(maxWidth: .infinity)
}
}
这就是一切的样子:
希望有帮助。