Sample Code
注: 2022年,SwiftUI 4.0时代,有些代码已经过时咯~
渐变进度条
ZStack(alignment: .leading) {
Capsule()
.fill(.tertiary)
.foregroundStyle(.white)
GeometryReader { proxy in
Capsule()
.fill(.linearGradient(.init(colors: [.orange, .red]), startPoint: .leading, endPoint: .trailing))
.frame(width: (cast.farenheit / 140) * proxy.size.width)
}
}
.frame(height:4)
$visibility.animation(.linear(duration: 5))
Toggle(isOn: $visibility.animation(.linear(duration: 5))) {
Text("Toggle Text Views")
}
.gesture(nil)
// A gesture recognizer can be removed from a view by passing a nil value to the gesture() modifier
// 吃掉手势事件的方法
.gesture(nil)
refreshable
ScrollView {
Text("hello world.")
}
.refreshable {
}
.push(from: .trailing)
Text("hello world.")
.transition(.push(from: .trailing))
AnyLayout
struct ContentView: View {
@State private var show: Bool = false
var body: some View {
ZStack {
let dynamicLayout = show ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
dynamicLayout {
Text("hello")
Text("world")
}
.onTapGesture {
withAnimation(.easeInOut(duration: 1)) {
show.toggle()
}
}
}
}
}
struct ContentView: View { //AnyLayout 另一个例子(官方)
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
let layout = dynamicTypeSize <= .medium ?
AnyLayout(HStack()) : AnyLayout(VStack())
layout {
Text("First label")
Text("Second label")
}
}
}
.contentTransition(.interpolate)
Text("hello world.")
.font(show ? .largeTitle : .body)
.fontWeight(show ? .black : .thin)
.foregroundColor(show ? .yellow : .red)
.contentTransition(.interpolate)
.onTapGesture {
withAnimation(.easeInOut(duration: 1)) {
show.toggle()
}
}
.toolbar(.hidden, for: .navigationBar)
NavigationStack {
Text("hello world.")
.navigationTitle("Title")
.toolbarBackground(Color.yellow, for: .navigationBar)
.toolbar(.hidden, for: .navigationBar)
}
MagnificationGesture
struct ContentView: View {
@State private var magnification: CGFloat = 1.0
var body: some View {
let magnificationGesture =
MagnificationGesture(minimumScaleDelta: 0)
.onChanged({ value in
self.magnification = value
})
.onEnded({ _ in
print("Gesture Ended")
})
return Image(systemName: "hand.point.right.fill")
.resizable()
.font(.largeTitle)
.scaleEffect(magnification)
.gesture(magnificationGesture)
.frame(width: 100, height: 90)
}
}
struct MagnificationGestureView: View {
@GestureState var magnifyBy = 1.0
var magnification: some Gesture {
MagnificationGesture()
.updating($magnifyBy) { currentState, gestureState, transaction in
gestureState = currentState
}
}
var body: some View {
Circle()
.frame(width: 100, height: 100)
.scaleEffect(magnifyBy)
.gesture(magnification)
}
}
transition
.transition(.scale)
.transition(.move(edge: .top))
.transition(AnyTransition.opacity.combined(with: .move(edge: .top)))
.transition(.fadeAndMove)
.transition(.asymmetric(insertion: .scale, removal: .slide))
extension AnyTransition {
static var fadeAndMove: AnyTransition {
AnyTransition.opacity.combined(with: .move(edge: .top))
}
}
ToggleStyle
struct ContentView: View {
@State private var active: Bool = false
var body: some View {
Toggle(isOn: $active, label: {
Text("Active")
})
.toggleStyle(PowerToggleStyle())
}
}
struct CheckmarkToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.label
Spacer()
Rectangle()
.foregroundColor(configuration.isOn ? .green : .gray)
.frame(width: 51, height: 31, alignment: .center)
.overlay(
Circle()
.foregroundColor(.white)
.padding(.all, 3)
.overlay(
Image(systemName: configuration.isOn ? "checkmark" : "xmark")
.resizable()
.aspectRatio(contentMode: .fit)
.font(Font.title.weight(.black))
.frame(width: 8, height: 8, alignment: .center)
.foregroundColor(configuration.isOn ? .green : .gray)
)
.offset(x: configuration.isOn ? 11 : -11, y: 0)
.animation(Animation.linear(duration: 0.1), value: configuration.isOn)
).cornerRadius(20)
.onTapGesture { configuration.isOn.toggle() }
}
}
}
struct PowerToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.label
Spacer()
Rectangle()
.foregroundColor(configuration.isOn ? .green : .gray)
.frame(width: 51, height: 31, alignment: .center)
.overlay(
Circle()
.foregroundColor(.white)
.padding(.all, 3)
.overlay(
GeometryReader { geo in
Path { p in
if !configuration.isOn {
p.addRoundedRect(in: CGRect(x: 20, y: 10, width: 10.5, height: 10.5), cornerSize: CGSize(width: 7.5, height: 7.5), style: .circular, transform: .identity)
} else {
p.move(to: CGPoint(x: 51/2, y: 10))
p.addLine(to: CGPoint(x: 51/2, y: 31-10))
}
}.stroke(configuration.isOn ? Color.green : Color.gray, lineWidth: 2)
}
)
.offset(x: configuration.isOn ? 11 : -11, y: 0)
.animation(Animation.linear(duration: 0.1), value: configuration.isOn)
).cornerRadius(20)
.onTapGesture { configuration.isOn.toggle() }
}
}
}
struct ImageToggleStyle: ToggleStyle {
var onImageName: String
var offImageName: String
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.label
Spacer()
Image(configuration.isOn ? onImageName : offImageName)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 51, height: 31, alignment: .center)
.overlay(
Circle()
.foregroundColor(.white)
.padding(.all, 3)
.offset(x: configuration.isOn ? 11 : -11, y: 0)
.animation(Animation.linear(duration: 0.1), value: configuration.isOn)
).cornerRadius(20)
.onTapGesture { configuration.isOn.toggle() }
}
}
}
ViewModifier
struct ContentView: View {
var body: some View {
Text("Active")
.standardTitle()
}
}
struct StandardTitle: ViewModifier {
func body(content: Content) -> some View {
content
.font(.largeTitle)
.padding()
.border(Color.gray, width: 2)
.background(Color.white
.shadow(color: Color.black, radius: 5, x: 0, y: 5))
}
}
extension View {
func standardTitle() -> some View {
self.modifier(StandardTitle())
}
}
@ViewBuilder
struct MyVStack<Content: View>: View {
let content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
var body: some View {
VStack(spacing: 10) {
content()
}
.font(.largeTitle)
}
}
horizontalSizeClass
struct ContentView: View {
@Environment(\.horizontalSizeClass) var horizontalSizeClass
var body: some View {
if horizontalSizeClass == .compact {
VStack {
Text("竖屏")
Text("shu ping")
}
} else {
HStack {
Text("横屏")
Text("heng ping")
}
}
}
}
@AppStorage
struct UserName: Encodable, Decodable {
var firstName: String
var secondName: String
}
var username = UserName(firstName: "Mark", secondName: "Wilson")
@AppStorage("username") var namestore: Data = Data()
let encoder = JSONEncoder()
if let data = try? encoder.encode(username) {
namestore = data
}
let decoder = JSONDecoder()
if let name = try? decoder.decode(UserName.self, from: namestore) {
username = name
}
@AppStorage("myimage") var imagestore: Data = Data()
var image = UIImage(named: "profilephoto")
if let data = image!.pngData() {
imagestore = data
}
if let decodedImage: UIImage = UIImage(data: imagestore) {
image = decodedImage
}
时间间隔
func minutesBetween(_ start: Date, and end: Date) -> Int {
let diff = Calendar.current.dateComponents([.minute], from: start, to: end)
guard let minute = diff.minute else { return 0 }
return abs(minute)
}
样式文本
func textWithHashtags(_ text: String, color: Color) -> Text {
let words = text.split(separator: " ")
var output: Text = Text("")
for word in words {
if word.hasPrefix("#") { // Pick out hash in words
output = output + Text(" ") + Text(String(word))
.foregroundColor(color) // Add custom styling here
} else {
output = output + Text(" ") + Text(String(word))
}
}
return output
}
struct ContentView: View {
var body: some View {
textWithHashtags("Here's a quick and easy way to include #hashtag formatting in your #SwiftUI Text Views. Learn how in the #Tutorial here.", color: .red)
.font(Font.custom("Avenir Next", size: 20))
.bold()
}
}
UUID
var id = UUID().uuidString
Spacer不占地儿
Spacer(minLength: 0)
Image 大小
.aspectRatio(contentMode: .fit)
Namespace的使用
@Namespace var animation // 外部声明
TabButton(title: tab, selected: $selected, animation: animation) // 传入
var animation: Namespace.ID // 内部定义
.matchedGeometryEffect(id: "image\(item.id)", in: animation) // 使用
ZStack的一种用法
.background(
ZStack {
if selected == title {
Color.black
}
}
)
好玩的图形
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
return Path { path in
let pt1 = CGPoint(x: rect.width, y: 0)
let pt2 = CGPoint(x: 0, y: rect.height - 100)
let pt3 = CGPoint(x: 0, y: rect.height)
let pt4 = CGPoint(x: rect.width, y: rect.height)
path.move(to: pt4)
path.addArc(tangent1End: pt1, tangent2End: pt2, radius: 20)
path.addArc(tangent1End: pt2, tangent2End: pt3, radius: 20)
path.addArc(tangent1End: pt3, tangent2End: pt4, radius: 20)
path.addArc(tangent1End: pt4, tangent2End: pt1, radius: 20)
}
}
}
最后一个不加哦
if tabs.last != tab {
Spacer(minLength: 0)
}
LazyVGrid
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 20), count: 2), spacing: 25, content: {
withAnimation
withAnimation(.spring()) {
}
安全区铺色
.background(
Color.white
.ignoresSafeArea()
)
异形底色
.background(
Color.orange
.clipShape(CustomShape())
)
多个选项,勾选的实现方法,ZStack比overlay好用
ForEach(1...4, id: \.self) { index in
ZStack {
Color("p\(index + 1)")
.clipShape(Circle())
.frame(width: 45, height: 45)
.onTapGesture {
withAnimation(.spring()) {
selectedColor = Color("p\(index + 1)")
}
}
if selectedColor == Color("p\(index + 1)") {
Image(systemName: "checkmark")
.foregroundColor(.black)
}
}
}
显示/隐藏,height的原始值设置nil即可
.frame(height: loadContent ? nil : 0)
.opacity(loadContent ? 1 : 0)
圆角矩形框
.background(
RoundedRectangle(cornerRadius: 15)
.stroke(Color.black, lineWidth: 1)
)
view出现时触发
.onAppear {
withAnimation(Animation.spring().delay(0.45)) {
loadContent.toggle()
}
}
在某种条件下使用scrollview
ScrollView(UIScreen.main.bounds.height < 750 ? .vertical : .init(), content: {
})
动画技术
- timingCurve
// If you need fine control over the animation curve’s shape, you can use the ==timingCurve(_:_:_:_)== type method.
- Creating spring animations
.animation(
.interpolatingSpring(
mass: 1,
stiffness: 100,
damping: 10,
initialVelocity: 0
)
)
/*
The parameters you pass are the same mentioned above:
• mass: Controls how long the system “bounces”.
• stiffness: Controls the speed of the initial movement.
• damping: Controls how fast the system slows down and stops.
• initialVelocity: Gives an extra initial motion.
*/
```
```swift
.animation(
.spring(
response: 0.55,
dampingFraction: 0.45,
blendDuration: 0
) )
- 延时
.animation(Animation.easeInOut.delay(Double(history.day) * 0.1))
- 封装Animation
func barAnimation(_ barNumber: Int) -> Animation {
return Animation.easeInOut.delay(Double(barNumber) * 0.1)
}
.animation(barAnimation(history.day))
var walkingAnimation: Animation {
Animation
.linear(duration: 3.0)
.repeatForever(autoreverses: false)
}
.animation(walkingAnimation)
- Transition
Group {
if showTerminal {
Text("Hide Terminal Map")
} else {
Text("Show Terminal Map")
}
}
.transition(.slide)
.transition(.move(edge: .bottom))
extension AnyTransition {
static var buttonNameTransition: AnyTransition {
AnyTransition.slide
}
}
if showTerminal {
FlightTerminalMap(flight: flight)
.transition(.buttonNameTransition)
}
- async transiton
extension AnyTransition {
static var buttonNameTransition: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
.combined(with: .opacity)
let removal = AnyTransition.scale(scale: 0.0)
.combined(with: .opacity)
return .asymmetric(insertion: insertion, removal: removal)
}
}
- Hero animation
.matchedGeometryEffect
异步任务的Button
struct AsyncButton<Content: View>: View {
var isComplete: Bool
let action: ()->()
let content: Content
@State private var inProgress: Bool
init(isComplete: Bool, action: @escaping ()->(), @ViewBuilder label: ()->Content) {
self.action = action
self.isComplete = isComplete
self.content = label()
self._inProgress = State.init(initialValue: false)
}
var body: some View {
Button(action: {
if !inProgress { action() }
withAnimation(Animation.easeInOut(duration: 0.4)) {
inProgress = true
}
}, label: {
VStack(alignment: .trailing) {
if inProgress && !isComplete {
ProgressView()
.foregroundColor(.white)
} else if isComplete {
Image(systemName: "checkmark")
.resizable()
.frame(width: 15, height: 15, alignment: .center)
.foregroundColor(.white)
} else {
content
}
}
.frame(maxWidth: isComplete || inProgress ? 50 : .infinity, maxHeight: isComplete || inProgress ? 50 : nil, alignment: .center)
.padding(.vertical, isComplete || inProgress ? 0 : 12)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(isComplete || inProgress ? 25 : 8)
.font(Font.body.weight(.semibold))
.padding(.all, 20)
})
}
}
struct ContentView: View {
@State var complete: Bool = false
@State var inProgress: Bool = false
var body: some View {
AsyncButton(isComplete: complete, action: {
inProgress = true
// Start Async Task (Download, Submit, etc)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
complete = true
}
}
}) {
Text(complete || inProgress ? "" : "Submit")
}
}
}
菜单/X按钮的动画实现
Button(action: {
withAnimation(.spring()){
showMenu.toggle()
}
}, label: {
VStack (spacing: 5){
Capsule()
.fill(showMenu ? Color.white : Color.primary)
.frame(width: 30, height: 3)
.rotationEffect(.init(degrees: showMenu ? -50 : 0))
.offset(x: showMenu ? 2 : 0, y: showMenu ? 9 : 0)
VStack (spacing:5) {
Capsule()
.fill(showMenu ? Color.white : Color.primary)
.frame(width: 30, height: 3)
Capsule()
.fill(showMenu ? Color.white : Color.primary)
.frame(width: 30, height: 3)
.offset(y: showMenu ? -8 : 0)
}
.rotationEffect(.init(degrees: showMenu ? 50 : 0))
}
})
.padding()
alignmentGuide的使用方法
VStack(alignment: .leading) {
Rectangle()
.foregroundColor(Color.green)
.frame(width: 120, height: 50)
Rectangle()
.foregroundColor(Color.red)
.alignmentGuide(.leading, computeValue: { d in 120.0 })
.frame(width: 200, height: 50)
Rectangle()
.foregroundColor(Color.blue)
.frame(width: 180, height: 50)
}
// .alignmentGuide(.leading, computeValue: { d in d.width / 3 })
// .alignmentGuide(.leading, computeValue: { d in d[HorizontalAlignment.trailing] + 20})
AnimatableModifier 废弃
用Animatable, ViewModifier
struct NumberView: Animatable, ViewModifier {
var number: Int
var animatableData: CGFloat {
get { CGFloat(number) }
set { number = Int(newValue) }
}
func body(content: Content) -> some View {
Text(String(number))
}
}
生命周期
struct LifecycleDemoApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase, perform: { phase in
switch phase {
case .active:
print("Active")
case .inactive:
print("Inactive")
case .background:
print("Background")
default:
print("Unknown scenephase")
}
})
}
}
ContentShapeKinds有什么用?
Image("leaf")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 300)
.clipShape(Circle())
.contentShape(ContentShapeKinds.contextMenuPreview, Circle())
.contextMenu {
Text("Menu Item")
}
contextMenu的用法
struct ContentView: View {
@State private var foregroundColor: Color = Color.black
@State private var backgroundColor: Color = Color.white
var body: some View {
Text("Hello, world!")
.font(.largeTitle)
.padding()
.foregroundColor(foregroundColor)
.background(backgroundColor)
.contextMenu {
Button(action: {
self.foregroundColor = .black
self.backgroundColor = .white
}) {
Text("Normal Colors")
Image(systemName: "paintbrush")
}
Button(action: {
self.foregroundColor = .white
self.backgroundColor = .black
}) {
Text("Inverted Colors")
Image(systemName: "paintbrush.fill")
}
}
}
}
@GestureState
- 三个例子
- 并使用了如下两种多手势组合使用的方法
- 同时:.simultaneously(with: DragGesture())
- 排队:.sequenced(before: DragGesture())
import SwiftUI
struct ContentView_testGestureState1: View {
@GestureState private var offset: CGSize = .zero
var body: some View {
let drag = DragGesture()
.updating($offset) { dragValue, state, transaction in
state = dragValue.translation
}
return Image(systemName: "hand.point.right.fill")
.font(.largeTitle)
.offset(offset)
.gesture(drag)
}
}
struct ContentView_testGestureState2: View {
@GestureState private var offset: CGSize = .zero
@GestureState private var longPress: Bool = false
var body: some View {
let longPressAndDrag = LongPressGesture(minimumDuration: 1.0)
.updating($longPress) { value, state, transition in
state = value
}
.simultaneously(with: DragGesture())
.updating($offset) { value, state, transaction in
state = value.second?.translation ?? .zero
}
return Image(systemName: "hand.point.right.fill")
.foregroundColor(longPress ? Color.red : Color.blue)
.font(.largeTitle)
.offset(offset)
.gesture(longPressAndDrag)
}
}
struct ContentView_testGestureState3: View {
@GestureState private var offset: CGSize = .zero
@State private var dragEnabled: Bool = false
var body: some View {
let longPressBeforeDrag = LongPressGesture(minimumDuration: 2.0)
.onEnded( { _ in
self.dragEnabled = true
})
.sequenced(before: DragGesture())
.updating($offset) { value, state, transaction in
switch value {
case .first(true):
print("Long press in progress")
case .second(true, let drag):
state = drag?.translation ?? .zero
default: break
}
}
.onEnded { value in
self.dragEnabled = false
}
return Image(systemName: "hand.point.right.fill")
.foregroundColor(dragEnabled ? Color.green : Color.blue)
.font(.largeTitle)
.offset(offset)
.gesture(longPressBeforeDrag)
}
}
DragGesture
struct ContentView: View {
enum SwipeHorizontalDirection: String {
case left, right, none
}
@State var swipeHorizontalDirection: SwipeHorizontalDirection = .none { didSet { print(swipeHorizontalDirection) } }
var body: some View {
VStack {
Text(swipeHorizontalDirection.rawValue)
}
.gesture(
DragGesture()
.onChanged {
if $0.startLocation.x > $0.location.x {
self.swipeHorizontalDirection = .left
} else if $0.startLocation.x == $0.location.x {
self.swipeHorizontalDirection = .none
} else {
self.swipeHorizontalDirection = .right
}
})
}
}
Parallelogram Shape and Mask
struct Parallelogram: Shape {
var depth: CGFloat
var flipped: Bool = false
func path(in rect: CGRect) -> Path {
Path { p in
if flipped {
p.move(to: CGPoint(x: 0, y: 0))
p.addLine(to: CGPoint(x: rect.width, y: depth))
p.addLine(to: CGPoint(x: rect.width, y: rect.height))
p.addLine(to: CGPoint(x: 0, y: rect.height - depth))
} else {
p.move(to: CGPoint(x: 0, y: depth))
p.addLine(to: CGPoint(x: rect.width, y: 0))
p.addLine(to: CGPoint(x: rect.width, y: rect.height - depth))
p.addLine(to: CGPoint(x: 0, y: rect.height))
}
p.closeSubpath()
}
}
}
struct ContentView: View {
var body: some View {
Image("pic")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 300, alignment: .center)
.mask(Parallelogram(depth: 50, flipped: false))
}
}
ScrollView Sticky Header
struct StickyHeader<Content: View>: View {
var minHeight: CGFloat
var content: Content
init(minHeight: CGFloat = 200, @ViewBuilder content: () -> Content) {
self.minHeight = minHeight
self.content = content()
}
var body: some View {
GeometryReader { geo in
if(geo.frame(in: .global).minY <= 0) {
content
.frame(width: geo.size.width, height: geo.size.height, alignment: .center)
} else {
content
.offset(y: -geo.frame(in: .global).minY)
.frame(width: geo.size.width, height: geo.size.height + geo.frame(in: .global).minY)
}
}.frame(minHeight: minHeight)
}
}
struct ContentView: View {
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
StickyHeader {
StickyHeader {
Image("pic")
.resizable()
.aspectRatio(contentMode: .fill)
}
}
// MARK: Scroll View Content Here
}
}
}
获取屏幕数据
extension View {
func getRect() -> CGRect {
return UIScreen.main.bounds
}
func getSageArea() -> UIEdgeInsets {
return UIApplication.shared.windows.first?.safeAreaInsets ?? UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
视图的init()和滚动视图的防回弹
init() { UIScrollView.appearance().bounces = false }
获得滚动视图的当前offset
.overlay(
GeometryReader { proxy -> Color in
DispatchQueue.main.async {
if startOffset == 0 {
self.startOffset = proxy.frame(in: .global).minY
}
let offset = proxy.frame(in: .global).minY
self.scrollViewOffset = offset - startOffset
}
return Color.clear
}
.frame(width: 0, height: 0, alignment: .top), alignment: .top)
双shadow
.shadow(color: Color.primary.opacity(0.1), radius: 5, x: 5, y: 5)
.shadow(color: Color.primary.opacity(0.1), radius: 5, x: -5, y: -5)
持续动画
func doAnimation(){
withAnimation(Animation.easeInOut(duration:0.6).repeatForever(autoreverses: true)) {
animateBall.toggle()
}
withAnimation(Animation.easeInOut(duration:0.8).repeatForever(autoreverses: true)) {
animateRotation.toggle()
}
}
3D效果实现圆到椭圆
Circle()
.fill(Color.gray.opacity(0.4))
.frame(width: animateBall ? 40 : 20, height: 40)
.rotation3DEffect(
.init(degrees: 70),
axis: (x: 1, y: 0, z: 0),
anchor: .center
)
3D效果
.rotation3DEffect(.init(degrees: getProgress() * 90), axis:(x:0, y: 1, z: 0), anchor: offset > 0 ?.leading : .trailing, anchorZ: 0, perspective: 0.5)
自定义圆角
struct CustomCorner: Shape {
var corners: UIRectCorner
var radius: CGFloat
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners,cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
用zIndex调整前后次序
.zIndex(1) // 后
.zIndex(0) // 前
@ViewBuilder
struct CustomStackView<Title: View, Content:View>: View {
var titleView: Title
var contentView: Content
init(@ViewBuilder titleView:@escaping () ->Title, @ViewBuilder contentView:@escaping () ->Content) {
self.contentView = contentView()
self.titleView = titleView()
}
控件颜色
.tint(.purple)
延迟动画
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation(.spring()) {
CustomCorner
struct CustomCorner: Shape {
var corners: UIRectCorner
var radius: CGFloat
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
struct ContentView: View {
var body: some View {
CustomCorner(corners: UIRectCorner.topLeft, radius: 30)
.fill(Color.purple)
.frame(width: 240, height: 80)
}
}
获取屏幕大小 / 0圆角的作用
GeometryReader { proxy in
let frame = proxy.frame(in: .global)
Image(selectedTab.image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: frame.width, height: frame.height, alignment: .center)
//why we using corner radius
//because image in SwiftUI using .fill
//will require corner radius or clipshape to cut the image
.cornerRadius(0)
}
.ignoresSafeArea()
当前值的判定
func getIndex() -> Int {
let index = trips.firstIndex { (trip) -> Bool in
return selectedTab.id == trip.id
} ?? 0
return index
}
多行文本居中
Text("Happy Birthday\nJustine")
.kerning(3)
.lineSpacing(10.0)
.multilineTextAlignment(.center)
数组中随机元素
.randomElement()
禁止控件连击
Button(...
.disabled(wish) // wish 变量控制
Timer / Transition
struct CubeRotationModifier: AnimatableModifier {
enum SlideDirection {
case enter
case exit
}
var pct: Double
var direction: SlideDirection
var animatableData: Double {
get { pct }
set { pct = newValue }
}
func body(content: Content) -> some View {
GeometryReader { geo in
content
.rotation3DEffect(
Angle(degrees: calcRotation()),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: direction == .enter ? .leading : .trailing,
anchorZ: 0,
perspective: 0.1
).transformEffect(.init(translationX: calcTranslation(geo: geo), y: 0))
}
}
func calcTranslation(geo: GeometryProxy) -> CGFloat {
if direction == .enter {
return geo.size.width - (CGFloat(pct) * geo.size.width)
} else {
return -1 * (CGFloat(pct) * geo.size.width)
}
}
func calcRotation() -> Double {
if direction == .enter {
return 90 - (pct * 90)
} else {
return -1 * (pct * 90)
}
}
}
extension AnyTransition {
static var cubeRotation: AnyTransition {
get {
AnyTransition.asymmetric(
insertion: AnyTransition.modifier(active: CubeRotationModifier(pct: 0, direction: .enter), identity: CubeRotationModifier(pct: 1, direction: .enter)),
removal: AnyTransition.modifier(active: CubeRotationModifier(pct: 1, direction: .exit), identity: CubeRotationModifier(pct: 0, direction: .exit)))
}
}
}
struct ContentView: View {
@State var index: Int = 0
@State var timer = Timer.publish(every: 2, on: RunLoop.main, in: RunLoop.Mode.common).autoconnect()
var colors: [Color] = [Color.red, Color.blue, Color.green, Color.orange, Color.purple]
var body: some View {
ZStack {
ForEach(colors.indices) { i in
if index == i {
colors[i]
.transition(.cubeRotation)
}
}
}
.frame(width: 200, height: 200, alignment: .center)
.onReceive(timer) { _ in
withAnimation(.easeInOut(duration: 1.3)) {
index = (index + 1) % colors.count
}
}
}
}
pinnedViews
ScrollView {
LazyVStack(alignment: .center, spacing: 40, pinnedViews: [.sectionHeaders], content: {
ForEach(0...50, id: \.self) { count in
Section(header: stickyHeaderView) {
Text("section \(count)")
}
}
})
}
.init()
@StateObject var lockscreenModel: LockscreenModel = .init()
PhotosPicker
PhotosPicker(selection: $lockscreenModel.pickedItem, matching: .images, preferredItemEncoding: .automatic, photoLibrary: .shared()) {
VStack(spacing: 10){
Image(systemName: "plus.viewfinder")
.font(.largeTitle)
Text("Add Image")
}
.foregroundColor(.primary)
}
PreferenceKey
Color.clear
.preference(key: RectKey.self, value: rect)
.onPreferenceChange(RectKey.self) { value in
lockscreenModel.textRect = value
}
//
struct RectKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
Date To String Conversion
enum DateFormat: String {
case hour = "hh"
case minute = "mm"
case seconds = "ss"
}
extension Date {
func converToString(_ format: DateFormat) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format.rawValue
return formatter.string(from: self)
}
}
像素点的Color值
extension UIView {
func color(at point: CGPoint) -> (CGFloat, CGFloat, CGFloat, CGFloat) {
let colorspace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
var pixelData: [UInt8] = [0,0,0,0]
let context = CGContext(data: &pixelData, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorspace, bitmapInfo: bitmapInfo.rawValue)
context!.translateBy(x: -point.x, y: -point.y)
self.layer.render(in: context!)
let red = CGFloat(pixelData[0]) / 255
let blue = CGFloat(pixelData[1]) / 255
let green = CGFloat(pixelData[2]) / 255
let alpha = CGFloat(pixelData[3]) / 255
return (red, blue, green, alpha)
}
}
screenSize
extension UIApplication {
func screenSize()->CGSize{
guard let window = connectedScenes.first as? UIWindowScene else { return .zero }
return window.screen.bounds.size
}
}
func makeUIView(context: Context) -> UIView {
let size = UIApplication.shared.screenSize()