Skip to content

Instantly share code, notes, and snippets.

@elislade
Last active March 28, 2022 16:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elislade/303447b6327536779e618ee7c60cbd41 to your computer and use it in GitHub Desktop.
Save elislade/303447b6327536779e618ee7c60cbd41 to your computer and use it in GitHub Desktop.
Swift UI Floating Window
struct ContentView: View {
@State var openWindow = false
var body: some View {
let window = FloatingWindow(
isOpen: $openWindow,
title: "Title",
button: Text("ƒx").font(.system(size: 18, weight: .bold, design: .monospaced)),
content: EmptyView()
).padding()
return
VStack {
Spacer()
HStack {
Spacer()
Text("Hello World")
Spacer()
}
Spacer()
}
.animation(nil)
.overlay(window, alignment: .bottomLeading)
.edgesIgnoringSafeArea(.all)
}
}
struct FloatingWindow<ButtonView:View, ContentView:View>: View {
@Binding var isOpen:Bool
@State var offset:CGSize = .zero
@State var tempOffset:CGSize = .zero
var title:String?
let button:ButtonView
let content:ContentView
let anim:Animation = Animation.spring().speed(1.8)
func toggle(){
withAnimation(self.anim) { self.isOpen.toggle() }
}
var body: some View {
VStack(spacing: 0) {
HStack {
Button(action: toggle){
button
if isOpen && title != nil {
Text(title!)
.fontWeight(.semibold)
.opacity(0.7)
.transition(.opacity)
}
}
.accentColor(.orange)
.frame(height: 54)
.frame(minWidth: 54)
.disabled(isOpen)
if isOpen {
Spacer()
Button(action: toggle){
Image(systemName: "arrow.down.right.and.arrow.up.left")
.rotationEffect(Angle(degrees: 90))
.foregroundColor(.orange)
.font(.system(size: 22, weight: .semibold, design: .monospaced))
}.transition(.opacity)
}
}
.padding(.horizontal, isOpen ? 14 : 0)
.background(LinearGradient.charcoal)
.zIndex(2)
.gesture(
DragGesture(coordinateSpace: .global).onChanged({ g in
self.offset = self.tempOffset + g.translation
}).onEnded({ g in
withAnimation(self.anim) {
self.offset.width = self.offset.width.clamp(0, 65)
self.offset.height = self.offset.height.clamp(-660, 0)
self.tempOffset = self.offset
}
})
)
if isOpen {
content
.zIndex(1)
.overlay(Divider(), alignment: .top)
.transition(.opacity)
}
}
.frame(maxWidth: isOpen ? 440 : 54)
.background(Color.black)
.colorScheme(.dark)
.cornerRadius(isOpen ? 10 : 27)
.shadow(radius: 10)
.overlay(
RoundedRectangle(cornerRadius: isOpen ? 10 : 27)
.stroke(lineWidth: 0.5)
.foregroundColor(Color.gray.opacity(0.6))
)
.offset(isOpen ? offset : .zero)
}
}
// Allows short hand for adding two CGSizes together
func +(lhs: CGSize, rhs: CGSize) -> CGSize {
CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height)
}
@frankusu
Copy link

Hi elislade, I'm getting a
Referencing operator function '+' on 'FloatingPoint' requires that 'CGSize' conform to 'FloatingPoint'
self.offset = self.tempOffset + g.translation

do you happen to know whats going on? Thank you

@elislade
Copy link
Author

elislade commented Mar 27, 2022

Sorry, I didn't include my custom CGSize operator. func +(lhs: CGSize, rhs: CGSize) -> CGSize { CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) }

@frankusu
Copy link

Thanks for response elislade !! Now there's another error
self.offset.width = self.offset.width.clamp(0, 65)
self.offset.height = self.offset.height.clamp(-660, 0)
Value of type 'CGFloat' has no member 'clamp'
hope you can help again tyty!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment