Increase tappable area Datepicker SwiftUI - swift

I'm trying to build a custom datepicker which is working perfectly so far. The last thing I try to increase is the tappable area, which I prefer to be the entire shape. Currently, a user has to tap the Calendar picture for date selection.
I've experimented with Contentshape, increasing the frame and adding padding, but nothing works as expected. How can I increase the tappable area while keeping it looking like this? Ideally, a user could tap the area within the border and the picker pops up.
My code:
struct DatePickerView: View {
#State private var selectedDate = Date()
var body: some View {
VStack(alignment: .leading) {
Text("Datum")
.foregroundColor(.black)
HStack {
Text("\(selectedDate.formatted())")
Spacer()
ZStack {
DatePicker("", selection: $selectedDate, in: ...Date(), displayedComponents: .date)
.datePickerStyle(.compact)
.labelsHidden()
.accentColor(.black)
SwiftUIWrapper {
Image(systemName: "calendar")
.resizable()
.frame(width: 18, height: 20)
}
.allowsHitTesting(false)
}
.frame(width: 18, height: 20)
}
.padding(16)
.overlay {
RoundedRectangle(cornerRadius: 8)
.strokeBorder(
style: StrokeStyle(
lineWidth: 0.5
)
)
.foregroundColor(.black)
}
}
}
}
struct DatePicker_Previews: PreviewProvider {
static var previews: some View {
DatePickerView()
}
}
struct SwiftUIWrapper<T: View>: UIViewControllerRepresentable {
let content: () -> T
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: content())
}
func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {}
}

Managed to make it work with some minor tweaks. Especially scaleEffect!
.compositingGroup()
.scaleEffect(x: 10, y: 1.5)
.clipped()
Endresult:
struct DatePickerView: View {
#State private var selectedDate = Date()
var body: some View {
VStack(alignment: .leading) {
Text("Datum")
.foregroundColor(.gray)
ZStack {
DatePicker("", selection: $selectedDate, in: ...Date(), displayedComponents: .date)
.datePickerStyle(.compact)
.labelsHidden()
.accentColor(.black)
.compositingGroup()
.scaleEffect(x: 10, y: 1.5)
.clipped()
SwiftUIWrapper {
HStack {
Text(selectedDate.formatted())
Spacer()
Image(systemName: "calendar")
.resizable()
.frame(width: 18, height: 20)
}
.padding(16)
.background(Color.white)
}
.padding(.vertical, 32)
.fixedSize(horizontal: false, vertical: true)
.allowsHitTesting(false)
}
.overlay (
RoundedRectangle(cornerRadius: 8)
.strokeBorder(
style: StrokeStyle(lineWidth: 0.5)
)
.foregroundColor(.black)
)
}
}
}
struct SwiftUIWrapper<T: View>: UIViewControllerRepresentable {
let content: () -> T
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: content())
}
func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {}
}
struct DatePicker_Previews: PreviewProvider {
static var previews: some View {
DatePickerView()
}
}

Related

Passing data in PreviewProvider SwiftUI, ObservableObject class, ObservedObject

I guys, I have a class with some properties (class ReservationInfo). In my first screen (ReservationFormView) I create an instance of the class(myReservationInfo).
I pass the data with a NavigationLink to the second view (ReservationRecapView). It works all right, but I have a problem with the SwiftUI preview. How can I pass some example data to the preview provider?
class ReservationInfo: ObservableObject {
#Published var customerName: String = ""
#Published var surname : String = ""
#Published var nPeople : Int = 1
#Published var date = Date()
}
struct ReservationFormView: View {
#StateObject var myReservationInfo = ReservationInfo()
#State private var showSecondView = false
init() {
UIStepper.appearance().setDecrementImage(UIImage(systemName: "minus"), for: .normal)
UIStepper.appearance().setIncrementImage(UIImage(systemName: "plus"), for: .normal)
}
var body: some View {
NavigationView{
VStack(alignment: .center) {
Text("Little lemon")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.gray)
.multilineTextAlignment(.center)
.padding(.bottom, 10.0)
Label("Reservation Form", systemImage: "fork.knife")
.foregroundColor(Color.gray)
.padding(.bottom, 30)
VStack(alignment: .center, spacing: 30.0){
TextField("Type your name", text: $myReservationInfo.customerName, onEditingChanged: {print($0)})
.onChange(of: myReservationInfo.customerName, perform: { newValue in print(newValue)})
.onSubmit {
print("submitted")
}
.underlineTextField()
TextField("Type your surname", text: $myReservationInfo.surname)
.underlineTextField()
HStack(alignment: .center, spacing: 30.0){
Label("\(myReservationInfo.nPeople)", systemImage: "person.fill")
.font(.title2)
Stepper("N di persone", value: $myReservationInfo.nPeople , in: 1...20)
.labelsHidden()
.accentColor(.blue)
}
.padding(.top)
DatePicker(
"Select date",
selection: $myReservationInfo.date,
in: Date.now...,
displayedComponents: [.date, .hourAndMinute]
)
.onChange(of: myReservationInfo.date, perform: {date in print(date)})
.padding(.bottom, 20.0)
}
NavigationLink{
ReservationRecapView(myReservationInfo: myReservationInfo)
} label: {
Text("Avanti")
.padding(.horizontal, 70.0)
}
.padding(.vertical, 10)
.background(Color.blue)
.cornerRadius(10)
.foregroundColor(.white)
}
.padding(.horizontal, 30.0)
.padding(.bottom, 100.0)
.environmentObject(myReservationInfo)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ReservationFormView()
}
}
}
extension Color {
static let darkPink = Color(red: 208 / 255, green: 45 / 255, blue: 208 / 255)
}
extension View {
func underlineTextField() -> some View {
self
.autocorrectionDisabled(true)
.overlay(Rectangle().frame(height: 2).padding(.top, 40))
.foregroundColor(.blue)
}
}
struct ReservationRecapView: View {
#ObservedObject var myReservationInfo : ReservationInfo
var body: some View {
VStack(alignment: .leading, spacing: 10.0){
Text(myReservationInfo.customerName)
Text(myReservationInfo.surname)
Text("Number of people \(myReservationInfo.nPeople)")
Text(myReservationInfo.date, style: .date)
Text(myReservationInfo.date, style: .time)
}.font(.title2)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ReservationRecapView(myReservationInfo: ReservationInfo())
}
}
I don't know how to pass date in the PreviewProvider

How to expand Detail View to full screen with SwiftUI?

I have a list view embedded in a Navigation View, however, this Navigation View is only about the screen height. This list links to a detailed view, and when a row is tapped, the Detail View only takes up half of the screen. I would like it to open a completely new window.
Screenshots:
The code is used is the following:
import SwiftUI
import CoreData
extension UIScreen{
static let screenWidth = UIScreen.main.bounds.size.width
static let screenHeight = UIScreen.main.bounds.size.height
static let screenSize = UIScreen.main.bounds.size
}
let topCardHeight: CGFloat = 350
struct HomeView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: SavedPoem.entity(), sortDescriptors: []) var savedpoems : FetchedResults<SavedPoem>
var body: some View {
VStack {
VStack (alignment: .center){
Text("Today's Poem, November 18th...")
.font(.subheadline)
.foregroundColor(.white)
.padding(.bottom)
.padding(.top, 75)
Text("No Man Is An Island")
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.white)
.padding(.bottom,1)
Text("by John Donne")
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.white)
.padding(.bottom, 35)
Button(action: {}) {
Text("Read Now")
.fontWeight(/*#START_MENU_TOKEN#*/.bold/*#END_MENU_TOKEN#*/)
.font(.subheadline)
.foregroundColor(.white)
.padding(15)
.border(Color.white, width: 3)
}
}
.frame(width: UIScreen.screenWidth, height: topCardHeight, alignment: .top)
.background(Color.black)
.edgesIgnoringSafeArea(.top)
.edgesIgnoringSafeArea(.bottom)
.padding(.bottom, 0)
NavigationView{
List{
ForEach(savedpoems, id:\.title) {SavedPoem in
NavigationLink (destination: ContentView()){
ZStack {
Rectangle().fill(Color.white)
.frame(width: UIScreen.main.bounds.width - 32, height: 70)
.cornerRadius(10).shadow(color: .gray, radius: 4)
HStack {
VStack (alignment: .leading){
Text("\(SavedPoem.title ?? "")").font(.headline)
.lineLimit(1)
Text("\(SavedPoem.author ?? "")").font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
}.padding()
}
// }.onDelete(perform: remove)
}
}
}
.navigationTitle("My Saved Poems")
.navigationBarHidden(true)
.edgesIgnoringSafeArea(.top)
.padding(.top, 0)
}
}
// func remove(at offsets : IndexSet) {
// for index in offsets {
// let delete = SavedPoem[index]
// self.moc.delete(delete)
// }
// try? self.moc.save()
// }
}
Any ideas? Thanks in advance.
If you need the same UI:
(The navigation view at the bottom of your top view) , here is a solution for it .
var body: some View {
#EnvironmentObject var sharedViewModel : SharedViewModel
VStack {
VStack (alignment: .center){
if sharedViewModel.currentPageIsHome {
// your top view body here ..
}
}
NavigationView{\*....*\}
}
}.onAppear {
sharedViewModel.currentPageIsHome = true
}.onDisappear {
sharedViewModel.currentPageIsHome = false
}
And you need to create an Observable object
class SharedViewModel: ObservableObject {
#Published var currentPageIsHome = false
}
And don't forget to initialize it in your SceneDelegate
ContentView().environmentObject(SharedViewModel())
Or
Clear version :
change your view hierarchy to :
NavigationView {
List{
Section(header: YourTopView()) {
// ... your list content
}
}
}

Make SwiftUI Rectangle same height or width as another Rectangle

For a SwiftUI layout in a macOS app, I have three Rectangles as shown below:
The code to produce this layout is:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
HStack {
ZStack {
Rectangle()
.fill(Color.purple)
.frame(width: 20)
Text("1")
.font(.subheadline)
.foregroundColor(.white)
}
ZStack {
Rectangle()
.fill(Color.orange)
Text("2")
.font(.subheadline)
.foregroundColor(.white)
}
}
HStack {
ZStack {
Rectangle()
.fill(Color.red)
.frame(height: 20)
Text("3")
.font(.subheadline)
.foregroundColor(.white)
}
}
}
.frame(minWidth: 400, minHeight: 250)
}
}
My objective is for Rectangle 1 to be the same height as Rectangle 2 and for Rectangle 3 to be the same width as Rectangle 2. The size relationships between the rectangles should stay the same as the window size is changed. When done correctly, the final result should look like the following:
How can I accomplish this in SwiftUI?
Here is a working approach, based on view preferences. Tested with Xcode 11.4 / macOS 10.15.6
struct ViewWidthKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
struct ContentView: View {
#State private var boxWidth = CGFloat.zero
var body: some View {
VStack {
HStack {
ZStack {
Rectangle()
.fill(Color.purple)
.frame(width: 20)
Text("1")
.font(.subheadline)
.foregroundColor(.white)
}
ZStack {
Rectangle()
.fill(Color.orange)
Text("2")
.font(.subheadline)
.foregroundColor(.white)
}
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
}
HStack {
ZStack {
Rectangle()
.fill(Color.red)
.frame(height: 20)
Text("3")
.font(.subheadline)
.foregroundColor(.white)
}.frame(width: boxWidth)
}.frame(maxWidth: .infinity, alignment: .bottomTrailing)
}
.onPreferenceChange(ViewWidthKey.self) { self.boxWidth = $0 }
.frame(minWidth: 400, minHeight: 250)
}
}

How do you blur the background in a SwiftUI macOS application?

I want to make the highlighted section transparent and blurred similar to other macOS applications. I found articles online on how to use an NSViewController to blur which I don't fully understand. I am new to swift and don't yet understand how to use Viewcontrollers. My code is below. Any help would be appreciated!
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { geometry in
NavigationView{
HStack(spacing: 0) {
ZStack{
Text("BitMessenger")
.font(.title)
.fontWeight(.light)
.foregroundColor(Color.white)
}
.frame(width: geometry.size.width/2, height: geometry.size.height+20)
.background(Color(red: 0.07, green: 0.07, blue: 0.07, opacity: 1.0))
VStack{
HStack {
Text("Sign Up")
.font(.headline)
.padding(.top, 30.0)
Spacer()
}
HStack {
Text("Welcome to BitMessenger")
.font(.subheadline)
.foregroundColor(Color.gray)
.padding(.top, 10.0)
Spacer()
}
Form {
VStack{
HStack {
Text("Full Name")
.font(.caption)
.foregroundColor(Color.white)
.padding(.top, 10.0)
Spacer()
}
TextField("ex. John Doe", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
HStack {
Text("Email Address")
.font(.caption)
.foregroundColor(Color.white)
.padding(.top, 10.0)
Spacer()
}
TextField("doejohn#example.com", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
HStack {
Text("Password")
.font(.caption)
.foregroundColor(Color.white)
.padding(.top, 10.0)
Spacer()
}
TextField("AIOFHWaowhf", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
HStack {
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Register")
.padding(.horizontal, 10.0)
}
.padding(.all)
}
}
}
.padding(.top)
Spacer()
NavigationLink(destination: ContentView()) {
Text("Already have an Account? Login")
.font(.caption)
.foregroundColor(Color.gray)
.background(Color.clear)
}
.padding(.bottom)
.foregroundColor(Color.clear)
}
.padding(.horizontal, 30.0)
.frame(width: geometry.size.width / 2, height: geometry.size.height+20)
.background(Color.black.opacity(0.9))
}.edgesIgnoringSafeArea(.all)
}
}
.edgesIgnoringSafeArea(.all)
.frame(width: 750.0, height: 500.0)
}
}
}
class MyViewController: NSViewController {
var visualEffect: NSVisualEffectView!
override func loadView() {
super.loadView()
visualEffect = NSVisualEffectView()
visualEffect.translatesAutoresizingMaskIntoConstraints = false
visualEffect.material = .dark
visualEffect.state = .active
visualEffect.blendingMode = .behindWindow
view.addSubview(visualEffect)
visualEffect.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
visualEffect.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
visualEffect.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
visualEffect.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You don't really need to subclass NSViewController. What you need is - NSVisualEffectView from AppKit,
A view that adds translucency and vibrancy effects to the views in your interface.
Since the NSVisualEffectView is not yet available in SwiftUI, you can wrap it using NSViewRepresentable pretty much like every AppKit control not available in SwiftUI.
You can do it like this -
import SwiftUI
struct VisualEffectView: NSViewRepresentable
{
let material: NSVisualEffectView.Material
let blendingMode: NSVisualEffectView.BlendingMode
func makeNSView(context: Context) -> NSVisualEffectView
{
let visualEffectView = NSVisualEffectView()
visualEffectView.material = material
visualEffectView.blendingMode = blendingMode
visualEffectView.state = NSVisualEffectView.State.active
return visualEffectView
}
func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context)
{
visualEffectView.material = material
visualEffectView.blendingMode = blendingMode
}
}
You can then use it as a standalone View-
VisualEffectView(material: NSVisualEffectView.Material.contentBackground, blendingMode: NSVisualEffectView.BlendingMode.withinWindow)
or use it as a background modifier to your highlighted VStack like this -
.background(VisualEffectView(material: NSVisualEffectView.Material.contentBackground, blendingMode: NSVisualEffectView.BlendingMode.withinWindow))
Go through the Apple Developer docs to learn more about the two blending modes. Also, play with the Material property to get the desired result.
This solution allows you to create a semiOpaqueWindow() type method that you can apply to a child of the View protocol, for example to a Rectangle shape here.
import SwiftUI
extension View {
public static func semiOpaqueWindow() -> some View {
VisualEffect().ignoresSafeArea()
}
}
struct VisualEffect : NSViewRepresentable {
func makeNSView(context: Context) -> NSView {
let view = NSVisualEffectView()
view.state = .active
return view
}
func updateNSView(_ view: NSView, context: Context) { }
}
struct ContentView : View {
var body: some View {
ZStack {
Rectangle.semiOpaqueWindow()
Text("Semi Transparent MacOS window")
}
}
}

Get rid of the standard onTouch Animation of a Button

When I implement a Button in SwiftUI this Button always has a standard animation when you click on it. A slight opacity animation. Is there a way to get rid of that animation?
Here is a simple example.
Here is a small video -> https://imgur.com/ClWd7YH
import SwiftUI
struct ContentView: View {
var body: some View {
VStack{
Button(action: {
// do somethinh
}) {
VStack{
ZStack{
Circle()
.fill(Color.white)
.frame(width: 45, height: 45, alignment: .center)
Image(systemName: "xmark.circle")
.foregroundColor(Color.black)
.font(Font.system(size: 18, weight: .thin))
.padding(6)
}
Text("Press")
.foregroundColor( Color.white)
}.frame(width: 300, height: 300, alignment: .center)
}
}.background(Color.green)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can define your own ButtonStyle which will override the default animation:
public struct ButtonWithoutAnimation: ButtonStyle {
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
}
Now just set the the style on the Button:
Button(action: { .. } {
..
}.buttonStyle(ButtonWithoutAnimation())
You can get what you want by creating your own buttonStyle. This way you have to specify an animation to use on tap. If you don't specify any animation the button won't have any animation. For example:
struct NoAnimationButton: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
}
struct ContentView: View {
var body: some View {
VStack{
Button(action: {
// do somethinh
}) {
VStack{
ZStack{
Circle()
.fill(Color.white)
.frame(width: 45, height: 45, alignment: .center)
Image(systemName: "xmark.circle")
.foregroundColor(Color.black)
.font(Font.system(size: 18, weight: .thin))
.padding(6)
}
Text("Press")
.foregroundColor( Color.white)
}
.frame(width: 300, height: 300, alignment: .center)
}
}
.background(Color.green)
.buttonStyle(NoAnimationButton())
}
}
Consider that you can always avoid using the Button view and implement the onTapGesture on your whole tappable view (or wherever you need it):
struct ContentView: View {
var body: some View {
VStack{
VStack{
ZStack{
Circle()
.fill(Color.white)
.frame(width: 45, height: 45, alignment: .center)
Image(systemName: "xmark.circle")
.foregroundColor(Color.black)
.font(Font.system(size: 18, weight: .thin))
.padding(6)
}
Text("Press")
.foregroundColor( Color.white)
}
.frame(width: 300, height: 300, alignment: .center)
}
.background(Color.green)
.onTapGesture {
//manage click
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
EDIT: Just to make an example of a buttonStyle with animation:
struct ScaleEffectButton: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.9 : 1)
}
}