Commit cbac0b93 authored by shigemi miura's avatar shigemi miura

iPad用テキスト入力画面

緯度経度表示
parent f588da6c
...@@ -63,6 +63,8 @@ ...@@ -63,6 +63,8 @@
02CE4DDA2ADFBA72002E79BC /* MapRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CE4DD92ADFBA72002E79BC /* MapRepresentable.swift */; }; 02CE4DDA2ADFBA72002E79BC /* MapRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CE4DD92ADFBA72002E79BC /* MapRepresentable.swift */; };
02F4DB672B2C173F00E86C41 /* SessionGetManualUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F4DB662B2C173F00E86C41 /* SessionGetManualUrl.swift */; }; 02F4DB672B2C173F00E86C41 /* SessionGetManualUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F4DB662B2C173F00E86C41 /* SessionGetManualUrl.swift */; };
A0F472F6BC78C5F1C5471836 /* Pods_SailAssistTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D252BA721FD314FAE4C2B4C /* Pods_SailAssistTests.framework */; }; A0F472F6BC78C5F1C5471836 /* Pods_SailAssistTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D252BA721FD314FAE4C2B4C /* Pods_SailAssistTests.framework */; };
D50533FA2EA7956900975ADD /* TransparentBackgroundViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50533F92EA7955500975ADD /* TransparentBackgroundViewModifier.swift */; };
D50533FC2EA8A93D00975ADD /* TextEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50533FB2EA8A93A00975ADD /* TextEditorView.swift */; };
D5056D3E2E8F4DE20076AC88 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D3D2E8F4DD80076AC88 /* SafariView.swift */; }; D5056D3E2E8F4DE20076AC88 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D3D2E8F4DD80076AC88 /* SafariView.swift */; };
D5056D482E925FE90076AC88 /* LayoutConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D472E925FDF0076AC88 /* LayoutConstants.swift */; }; D5056D482E925FE90076AC88 /* LayoutConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D472E925FDF0076AC88 /* LayoutConstants.swift */; };
D5056D522E9343300076AC88 /* TaskTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D512E9343270076AC88 /* TaskTitleView.swift */; }; D5056D522E9343300076AC88 /* TaskTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D512E9343270076AC88 /* TaskTitleView.swift */; };
...@@ -255,6 +257,8 @@ ...@@ -255,6 +257,8 @@
B422ACD189FD390D51F10007 /* Pods-SailAssistTests.qc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SailAssistTests.qc.xcconfig"; path = "Target Support Files/Pods-SailAssistTests/Pods-SailAssistTests.qc.xcconfig"; sourceTree = "<group>"; }; B422ACD189FD390D51F10007 /* Pods-SailAssistTests.qc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SailAssistTests.qc.xcconfig"; path = "Target Support Files/Pods-SailAssistTests/Pods-SailAssistTests.qc.xcconfig"; sourceTree = "<group>"; };
C0CBB553F04E0BB4ACC72BD1 /* Pods-Sailassist.qc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sailassist.qc.xcconfig"; path = "Target Support Files/Pods-Sailassist/Pods-Sailassist.qc.xcconfig"; sourceTree = "<group>"; }; C0CBB553F04E0BB4ACC72BD1 /* Pods-Sailassist.qc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sailassist.qc.xcconfig"; path = "Target Support Files/Pods-Sailassist/Pods-Sailassist.qc.xcconfig"; sourceTree = "<group>"; };
CA31D32D54D7C200EF057679 /* Pods-SailAssistTests.canary.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SailAssistTests.canary.xcconfig"; path = "Target Support Files/Pods-SailAssistTests/Pods-SailAssistTests.canary.xcconfig"; sourceTree = "<group>"; }; CA31D32D54D7C200EF057679 /* Pods-SailAssistTests.canary.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SailAssistTests.canary.xcconfig"; path = "Target Support Files/Pods-SailAssistTests/Pods-SailAssistTests.canary.xcconfig"; sourceTree = "<group>"; };
D50533F92EA7955500975ADD /* TransparentBackgroundViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransparentBackgroundViewModifier.swift; sourceTree = "<group>"; };
D50533FB2EA8A93A00975ADD /* TextEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEditorView.swift; sourceTree = "<group>"; };
D5056D3D2E8F4DD80076AC88 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; }; D5056D3D2E8F4DD80076AC88 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
D5056D472E925FDF0076AC88 /* LayoutConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutConstants.swift; sourceTree = "<group>"; }; D5056D472E925FDF0076AC88 /* LayoutConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutConstants.swift; sourceTree = "<group>"; };
D5056D512E9343270076AC88 /* TaskTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskTitleView.swift; sourceTree = "<group>"; }; D5056D512E9343270076AC88 /* TaskTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskTitleView.swift; sourceTree = "<group>"; };
...@@ -673,6 +677,8 @@ ...@@ -673,6 +677,8 @@
02A1DE2D2AFB497B005BCF55 /* View */ = { 02A1DE2D2AFB497B005BCF55 /* View */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D50533FB2EA8A93A00975ADD /* TextEditorView.swift */,
D50533F92EA7955500975ADD /* TransparentBackgroundViewModifier.swift */,
020B98622ADD14E40029DE4C /* ChatView.swift */, 020B98622ADD14E40029DE4C /* ChatView.swift */,
02A1DE2E2AFB4AA0005BCF55 /* ChatInputView.swift */, 02A1DE2E2AFB4AA0005BCF55 /* ChatInputView.swift */,
02C3E5D02AFCC16800AF7837 /* ChatTitleView.swift */, 02C3E5D02AFCC16800AF7837 /* ChatTitleView.swift */,
...@@ -1111,6 +1117,7 @@ ...@@ -1111,6 +1117,7 @@
025C28002B034A1900BADC49 /* PDFViewer.swift in Sources */, 025C28002B034A1900BADC49 /* PDFViewer.swift in Sources */,
02C3E6092AFDF30000AF7837 /* ChatMemberView.swift in Sources */, 02C3E6092AFDF30000AF7837 /* ChatMemberView.swift in Sources */,
020B98412AD8C3810029DE4C /* LoginView.swift in Sources */, 020B98412AD8C3810029DE4C /* LoginView.swift in Sources */,
D50533FC2EA8A93D00975ADD /* TextEditorView.swift in Sources */,
D5AE351D2AEBA6FC00059889 /* SessionLogin.swift in Sources */, D5AE351D2AEBA6FC00059889 /* SessionLogin.swift in Sources */,
D5AF8A522E89252400BECA22 /* MenuMainView.swift in Sources */, D5AF8A522E89252400BECA22 /* MenuMainView.swift in Sources */,
D5258C9F2B03527400365276 /* ResGetMessages.swift in Sources */, D5258C9F2B03527400365276 /* ResGetMessages.swift in Sources */,
...@@ -1200,6 +1207,7 @@ ...@@ -1200,6 +1207,7 @@
02CE4D892ADF62E1002E79BC /* EcaSettingView.swift in Sources */, 02CE4D892ADF62E1002E79BC /* EcaSettingView.swift in Sources */,
025F99722B2AE3AF00C9A18A /* TaskViewModel.swift in Sources */, 025F99722B2AE3AF00C9A18A /* TaskViewModel.swift in Sources */,
D54D17512B35923400A0EAA5 /* GetManualUrl.swift in Sources */, D54D17512B35923400A0EAA5 /* GetManualUrl.swift in Sources */,
D50533FA2EA7956900975ADD /* TransparentBackgroundViewModifier.swift in Sources */,
D5258CA32B036CC500365276 /* SessionGetMessage.swift in Sources */, D5258CA32B036CC500365276 /* SessionGetMessage.swift in Sources */,
D52C2C082B9195A8003B286C /* MenuInformationView.swift in Sources */, D52C2C082B9195A8003B286C /* MenuInformationView.swift in Sources */,
D5258C992B0334BF00365276 /* SessionShipStatus.swift in Sources */, D5258C992B0334BF00365276 /* SessionShipStatus.swift in Sources */,
......
...@@ -4,18 +4,31 @@ import Speech ...@@ -4,18 +4,31 @@ import Speech
struct ChatInputView: View { struct ChatInputView: View {
@EnvironmentObject private var sceneDelegate: SceneDelegate @EnvironmentObject private var sceneDelegate: SceneDelegate
@StateObject private var speechRecognizer = SpeechRecognizer() @StateObject private var speechRecognizer = SpeechRecognizer()
@StateObject private var viewModel: ChatInputViewModel
@State private var imageData: Data = .init(capacity: 0) @State private var imageData: Data = .init(capacity: 0)
@State private var source: UIImagePickerController.SourceType = .photoLibrary @State private var source: UIImagePickerController.SourceType = .photoLibrary
@FocusState var isKeyboardFocused: Bool @FocusState private var isKeyboardFocused: Bool
@ObservedObject var viewModel: ChatInputViewModel
@Binding private var isFocus: Bool @Binding private var isFocus: Bool
@Binding var isUploadingDialogPresented: Bool @Binding private var isUploadingDialogPresented: Bool
@Binding private var isPresentingText: Bool
init(isFocus: Binding<Bool>, isUploadingDialogPresented: Binding<Bool>) { init(
viewModel: ChatInputViewModel,
isFocus: Binding<Bool>,
isUploadingDialogPresented: Binding<Bool>,
isPresentingText: Binding<Bool>)
{
self.viewModel = viewModel
self._isFocus = isFocus self._isFocus = isFocus
self._isUploadingDialogPresented = isUploadingDialogPresented self._isUploadingDialogPresented = isUploadingDialogPresented
self._viewModel = StateObject(wrappedValue: ChatInputViewModel()) self._isPresentingText = isPresentingText
}
private var isIpad: Bool {
UIDevice.current.userInterfaceIdiom == .pad
} }
var body: some View { var body: some View {
...@@ -31,6 +44,7 @@ struct ChatInputView: View { ...@@ -31,6 +44,7 @@ struct ChatInputView: View {
sceneDelegate.tabWindow?.isHidden = true sceneDelegate.tabWindow?.isHidden = true
source = .camera source = .camera
viewModel.handleMediaInput(type: .camera) viewModel.handleMediaInput(type: .camera)
isPresentingText = false
} label: { } label: {
Image(systemName: "camera") Image(systemName: "camera")
.resizable() .resizable()
...@@ -44,6 +58,7 @@ struct ChatInputView: View { ...@@ -44,6 +58,7 @@ struct ChatInputView: View {
sceneDelegate.tabWindow?.isHidden = true sceneDelegate.tabWindow?.isHidden = true
source = .photoLibrary source = .photoLibrary
viewModel.handleMediaInput(type: .photoLibrary) viewModel.handleMediaInput(type: .photoLibrary)
isPresentingText = false
} label: { } label: {
Label("Photo Library", systemImage: "photo.on.rectangle") Label("Photo Library", systemImage: "photo.on.rectangle")
} }
...@@ -67,6 +82,9 @@ struct ChatInputView: View { ...@@ -67,6 +82,9 @@ struct ChatInputView: View {
if viewModel.isRecording { if viewModel.isRecording {
speechRecognizer.transcribedText = "" speechRecognizer.transcribedText = ""
speechRecognizer.startRecording() speechRecognizer.startRecording()
if isIpad {
isPresentingText = true
}
} else { } else {
speechRecognizer.stopRecording() speechRecognizer.stopRecording()
viewModel.inputText = speechRecognizer.transcribedText viewModel.inputText = speechRecognizer.transcribedText
...@@ -78,53 +96,66 @@ struct ChatInputView: View { ...@@ -78,53 +96,66 @@ struct ChatInputView: View {
.padding(5) .padding(5)
} }
//MARK: - テキスト入力 //MARK: - テキスト入力開始ボタン
TextEditor(text: $viewModel.inputText) if isIpad {
.focused($isKeyboardFocused) Button {
.font(FontStyle.DefaultText.font) isPresentingText = true
.scrollContentBackground(Visibility.hidden) } label: {
.foregroundColor(ColorSet.BodyChat.color) Image(systemName: isPresentingText ? "message.fill" : "message")
.background(ColorSet.ChatBaloon.color) .resizable()
.cornerRadius(5) .frame(width: 20, height: 20)
.frame(height: viewModel.textViewHeight) .padding(5)
.onAppear {
viewModel.recalculateHeight()
}
.onChange(of: viewModel.inputText) { _ in
viewModel.recalculateHeight()
} }
.overlay(alignment: .topLeading) { } else {
if viewModel.inputText.isEmpty { TextEditor(text: $viewModel.inputText)
Text("Enter your message") .focused($isKeyboardFocused)
.font(FontStyle.DefaultText.font) .font(FontStyle.DefaultText.font)
.foregroundColor(ColorSet.BodyNotice.color) .scrollContentBackground(Visibility.hidden)
.background(ColorSet.ChatBaloon.color) .foregroundColor(ColorSet.BodyChat.color)
.padding(10) .background(ColorSet.ChatBaloon.color)
.cornerRadius(5)
.frame(height: viewModel.textViewHeight)
.onAppear {
viewModel.recalculateHeight()
} }
} .onChange(of: viewModel.inputText) { _ in
.padding(EdgeInsets(top: 2, leading: 10, bottom: 3, trailing: 0)) viewModel.recalculateHeight()
.onChange(of: isKeyboardFocused) { isFocused in }
sceneDelegate.tabWindow?.isHidden = isFocused .overlay(alignment: .topLeading) {
viewModel.isKeyboardFocused = isFocused if viewModel.inputText.isEmpty {
isFocus = isFocused Text("Enter your message")
} .font(FontStyle.DefaultText.font)
.onChange(of: viewModel.isKeyboardFocused) { isFocused in .foregroundColor(ColorSet.BodyNotice.color)
isKeyboardFocused = isFocused .background(ColorSet.ChatBaloon.color)
} .padding(10)
}
}
.padding(EdgeInsets(top: 2, leading: 10, bottom: 3, trailing: 0))
.onChange(of: isKeyboardFocused) { isFocused in
sceneDelegate.tabWindow?.isHidden = isFocused
viewModel.isKeyboardFocused = isFocused
isFocus = isFocused
}
.onChange(of: viewModel.isKeyboardFocused) { isFocused in
isKeyboardFocused = isFocused
}
}
//MARK: - 送信ボタン //MARK: - 送信ボタン
Button { if !isIpad {
viewModel.sendChatMessage() Button {
} label: { viewModel.sendChatMessage()
Image("send") } label: {
.resizable() Image("send")
.frame(width: 20, height: 20) .resizable()
.padding(5) .frame(width: 20, height: 20)
.opacity(viewModel.inputText.isEmpty ? 0.5 : 1.0) .padding(5)
.opacity(viewModel.inputText.isEmpty ? 0.5 : 1.0)
}
.padding(.vertical, 6)
.padding(.trailing, 11)
.disabled(viewModel.inputText.isEmpty)
} }
.padding(.vertical, 6)
.padding(.trailing, 11)
.disabled(viewModel.inputText.isEmpty)
} }
.background(ColorSet.BackgroundSecondary.color) .background(ColorSet.BackgroundSecondary.color)
...@@ -225,8 +256,3 @@ struct ChatInputView: View { ...@@ -225,8 +256,3 @@ struct ChatInputView: View {
} }
} }
} }
#Preview {
ChatInputView(isFocus: .constant(false), isUploadingDialogPresented: .constant(false))
.environmentObject(SceneDelegate())
}
...@@ -16,6 +16,8 @@ struct ChatView: View { ...@@ -16,6 +16,8 @@ struct ChatView: View {
@State private var totalMediaCount: Int = 0 @State private var totalMediaCount: Int = 0
@State private var isMediaLoading: Bool = false @State private var isMediaLoading: Bool = false
@State private var isUploadingDialogPresented: Bool = false @State private var isUploadingDialogPresented: Bool = false
@ObservedObject var viewModel: ChatInputViewModel
@Binding var isPresentingText: Bool
var contentWidth: CGFloat var contentWidth: CGFloat
var body: some View { var body: some View {
...@@ -51,7 +53,10 @@ struct ChatView: View { ...@@ -51,7 +53,10 @@ struct ChatView: View {
.padding(.top) .padding(.top)
.onAppear { .onAppear {
guard !message.messages.isEmpty, guard !message.messages.isEmpty,
let id = message.messages.last?.messageId else { return } let id = message.messages.last?.messageId else {
isMediaLoading = true
return
}
totalMediaCount = message.messages.reduce(0) { count, msg in totalMediaCount = message.messages.reduce(0) { count, msg in
count + (msg.type >= 0 ? 1 : 0) count + (msg.type >= 0 ? 1 : 0)
...@@ -129,16 +134,21 @@ struct ChatView: View { ...@@ -129,16 +134,21 @@ struct ChatView: View {
message.viewCnt = 0 message.viewCnt = 0
} }
} }
ChatInputView(isFocus: $isFocus, isUploadingDialogPresented: $isUploadingDialogPresented) ChatInputView(
viewModel: viewModel,
isFocus: $isFocus,
isUploadingDialogPresented: $isUploadingDialogPresented,
isPresentingText: $isPresentingText
)
} }
if !isMediaLoading { if !isMediaLoading {
LoadingView() LoadingView()
} }
if isUploadingDialogPresented { // if isUploadingDialogPresented {
// UpLoadingView() // UpLoadingView()
} // }
} }
.background(ColorSet.BackgroundPrimary.color) .background(ColorSet.BackgroundPrimary.color)
} }
...@@ -235,8 +245,3 @@ struct AlertChatMessage: View { ...@@ -235,8 +245,3 @@ struct AlertChatMessage: View {
} }
} }
} }
#Preview {
ChatView(contentWidth: CGFloat())
}
import SwiftUI
struct TextEditorView: View {
@Binding var inputText: String
@Binding var isPresenting: Bool
var onSend: () -> Void
@FocusState private var isKeyboardFocused: Bool
var body: some View {
VStack {
VStack(spacing: 1) {
TextEditor(text: $inputText)
.focused($isKeyboardFocused)
.frame(height: 150)
.padding()
.font(FontStyle.DefaultText.font)
.scrollContentBackground(Visibility.hidden)
.foregroundColor(ColorSet.BodyChat.color)
.background(ColorSet.ChatBaloon.color)
.cornerRadius(5)
.onAppear {
isKeyboardFocused = true
}
HStack {
Button("Close") {
isPresenting = false
isKeyboardFocused = false
inputText = ""
}
.padding()
.background(ColorSet.BackgroundSecondary.color)
.cornerRadius(8)
Spacer()
Button {
onSend()
} label: {
Image("send")
.resizable()
.frame(width: 20, height: 20)
.padding(5)
.opacity(inputText.isEmpty ? 0.5 : 1.0)
}
.padding()
.background(ColorSet.BackgroundSecondary.color)
.foregroundColor(.white)
.cornerRadius(8)
.disabled(inputText.isEmpty)
}
.padding([.leading, .trailing], 1)
}
.frame(width: 250)
.padding()
.background(ColorSet.BackgroundSecondary.color)
.cornerRadius(12)
.shadow(radius: 10)
}
}
}
import SwiftUI
struct TransparentBackgroundViewModifier: ViewModifier {
func body(content: Content) -> some View {
content
.background(BackgroundClearView())
}
struct BackgroundClearView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
return UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
DispatchQueue.main.async {
uiViewController.view.superview?.superview?.backgroundColor = .clear
}
}
}
}
extension View {
func transparentBackground() -> some View {
self.modifier(TransparentBackgroundViewModifier())
}
}
...@@ -13,16 +13,24 @@ struct MapInformation: View { ...@@ -13,16 +13,24 @@ struct MapInformation: View {
var body: some View { var body: some View {
VStack { VStack {
VStack { VStack(alignment: .trailing, spacing: 5) {
Text(presentLocation()) Text(presentLocation())
.font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular))) .font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.foregroundColor(Color.black) .foregroundColor(Color.black)
.padding(5) Text("Heading:" + String(ship.heading) + "[°]")
.background(Color.white.opacity(0.7)) .font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.cornerRadius(3) .foregroundColor(Color.black)
Text("Course:" + String(ship.course) + "[°]")
.font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.foregroundColor(Color.black)
Text("Speed:" + String(ship.speed) + "[knot]")
.font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.foregroundColor(Color.black)
} }
.padding(.top, 100) .padding(10)
.padding(.trailing, positionLocation()) .background(Color.white.opacity(0.7))
.cornerRadius(3)
VStack { VStack {
ForEach(ngaData.ngaArea.map{ $0.1 }.filter{ $0.passingCnt > 0 }, id: \.name) { nga in ForEach(ngaData.ngaArea.map{ $0.1 }.filter{ $0.passingCnt > 0 }, id: \.name) { nga in
Text("Entering NGA \(nga.name)") Text("Entering NGA \(nga.name)")
...@@ -35,8 +43,10 @@ struct MapInformation: View { ...@@ -35,8 +43,10 @@ struct MapInformation: View {
} }
Spacer() Spacer()
} }
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
} }
//MARK: - 地図上の船情報
func presentLocation() -> String { func presentLocation() -> String {
var location = " 00°00.000'N\n000°00.000'E" var location = " 00°00.000'N\n000°00.000'E"
if let shipPos = ship.location { if let shipPos = ship.location {
...@@ -44,18 +54,8 @@ struct MapInformation: View { ...@@ -44,18 +54,8 @@ struct MapInformation: View {
} }
return location return location
} }
func positionLocation() -> CGFloat {
var yPos: CGFloat = 200
if UIDevice.current.userInterfaceIdiom == .pad {
yPos = 900
}
return yPos
}
} }
#Preview { #Preview {
MapInformation() MapInformation()
} }
...@@ -110,20 +110,27 @@ class MapViewController : UIViewController { ...@@ -110,20 +110,27 @@ class MapViewController : UIViewController {
internal var mapView: MapView! internal var mapView: MapView!
internal var touchedFeatures: Feature? = nil internal var touchedFeatures: Feature? = nil
private var lastVisibleRegion: CoordinateBounds?
private var previousZoomLevel: Double = 2.0
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
let centerCoordinate = CLLocationCoordinate2D(latitude: 37.8, longitude: -96) let centerCoordinate = CLLocationCoordinate2D(latitude: 37.8, longitude: -96)
// let url = StyleURI(rawValue: "mapbox://styles/jmarinecloud/cltrwnk5i01j901ra3g4n7dtz")K
// let options = MapInitOptions(cameraOptions: CameraOptions(center: centerCoordinate, zoom: 2), styleURI: url)
let options = MapInitOptions(cameraOptions: CameraOptions(center: centerCoordinate, zoom: 2)) let options = MapInitOptions(cameraOptions: CameraOptions(center: centerCoordinate, zoom: 2))
mapView = MapView(frame: view.bounds, mapInitOptions: options) mapView = MapView(frame: view.bounds, mapInitOptions: options)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(mapView) self.view.addSubview(mapView)
mapView.mapboxMap.onNext(event: .mapLoaded) { [self] _ in mapView.mapboxMap.onNext(event: .mapLoaded) { [self] _ in
self.addIconImage() self.addIconImage()
self.addLayers() self.addLayers()
self.latitudeLongitudeLinesLayer()
self.latitudeLongitudeLabelsLayer()
mapView.mapboxMap.onEvery(event: .cameraChanged) { _ in
self.updateOnViewportOrZoomChange()
}
} }
//MARK: - 地図上クリック //MARK: - 地図上クリック
...@@ -255,6 +262,8 @@ class MapViewController : UIViewController { ...@@ -255,6 +262,8 @@ class MapViewController : UIViewController {
case AlertAreaSymbol case AlertAreaSymbol
case AlertAreaLine case AlertAreaLine
case AlertAreaFill case AlertAreaFill
case LatitudeLongitudeLines
case LatitudeLongitudeLabels
} }
var ownShipSymbol = MapSource(layer: .OwnShipSymbol) var ownShipSymbol = MapSource(layer: .OwnShipSymbol)
...@@ -273,6 +282,8 @@ class MapViewController : UIViewController { ...@@ -273,6 +282,8 @@ class MapViewController : UIViewController {
var alertAreaFill = MapSource(layer: .AlertAreaFill) var alertAreaFill = MapSource(layer: .AlertAreaFill)
var alertAreaLine = MapSource(layer: .AlertAreaLine) var alertAreaLine = MapSource(layer: .AlertAreaLine)
var alertAreaSymbol = MapSource(layer: .AlertAreaSymbol) var alertAreaSymbol = MapSource(layer: .AlertAreaSymbol)
var latitudeLongitudeLines = MapSource(layer: .LatitudeLongitudeLines)
var latitudeLongitudeLabels = MapSource(layer: .LatitudeLongitudeLabels)
/** /**
* レイヤー作成 * レイヤー作成
...@@ -294,6 +305,289 @@ class MapViewController : UIViewController { ...@@ -294,6 +305,289 @@ class MapViewController : UIViewController {
ownShipSymbolLayer() //自船シンボル ownShipSymbolLayer() //自船シンボル
} }
//MARK: - 地図が移動した場合の再描画
private func updateOnViewportOrZoomChange() {
let currentZoomLevel = mapView.mapboxMap.cameraState.zoom
let currentVisibleRegion = mapView.mapboxMap.coordinateBounds(for: mapView.frame)
let interval = getScaleIntervals()
if let lastRegion = lastVisibleRegion {
if abs(currentVisibleRegion.southwest.latitude - lastRegion.southwest.latitude) < interval.latitudeInterval &&
abs(currentVisibleRegion.northeast.latitude - lastRegion.northeast.latitude) < interval.latitudeInterval &&
abs(currentVisibleRegion.southwest.longitude - lastRegion.southwest.longitude) < interval.longitudeInterval &&
abs(currentVisibleRegion.northeast.longitude - lastRegion.northeast.longitude) < interval.longitudeInterval &&
Int(currentZoomLevel) == Int(previousZoomLevel) {
return
}
}
previousZoomLevel = currentZoomLevel
lastVisibleRegion = currentVisibleRegion
self.latitudeLongitudeLinesLayer()
self.latitudeLongitudeLabelsLayer()
}
//MARK: - 地図の縮尺による間隔
private func getScaleIntervals() -> (latitudeInterval: Double, longitudeInterval: Double) {
let zoomLevel = mapView.mapboxMap.cameraState.zoom
print(debug: "zoomLevel:\(zoomLevel)")
let latitudeInterval: Double
let longitudeInterval: Double
//0〜22
if zoomLevel > 20 {
latitudeInterval = 0.5
longitudeInterval = 0.5
} else if zoomLevel > 12 {
latitudeInterval = 1.0
longitudeInterval = 1.0
} else if zoomLevel > 8 {
latitudeInterval = 5.0
longitudeInterval = 5.0
} else if zoomLevel > 15.0 {
latitudeInterval = 10.0
longitudeInterval = 10.0
} else {
latitudeInterval = 10.0
longitudeInterval = 10.0
}
return(latitudeInterval, longitudeInterval)
}
//MARK: - 画面サイズ+αの描画範囲
private func expandCoordinateBounds(_ bounds: CoordinateBounds, byDegrees degrees: Double) -> CoordinateBounds {
var expandedSouthwest = bounds.southwest
var expandedNortheast = bounds.northeast
expandedSouthwest.latitude = clampLatitude(expandedSouthwest.latitude - degrees)
expandedSouthwest.longitude = wrapLongitude(expandedSouthwest.longitude - degrees)
expandedNortheast.latitude = clampLatitude(expandedNortheast.latitude + degrees)
expandedNortheast.longitude = wrapLongitude(expandedNortheast.longitude + degrees)
return CoordinateBounds(southwest: expandedSouthwest, northeast: expandedNortheast)
}
//MARK: - 緯度経度線
private func latitudeLongitudeLinesLayer() {
let visibleRegion = mapView.mapboxMap.coordinateBounds(for: mapView.frame)
let interval = getScaleIntervals()
let expandedRegion = expandCoordinateBounds(visibleRegion, byDegrees: interval.latitudeInterval)
// GeoJSON生成をバックグラウンドで実行
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
let geoJSON = generateLatitudeLongitudeLinesForVisibleRegion(
minLatitude: expandedRegion.southwest.latitude,
maxLatitude: expandedRegion.northeast.latitude,
minLongitude: expandedRegion.southwest.longitude,
maxLongitude: expandedRegion.northeast.longitude,
latitudeInterval: interval.latitudeInterval,
longitudeInterval: interval.longitudeInterval
)
// メインスレッドで地図に反映
DispatchQueue.main.async {
print(debug: "geoJSON:\(geoJSON)")
let sourceId = self.latitudeLongitudeLines.sourceId
// 既存のソースがある場合は更新
if self.mapView.mapboxMap.style.sourceExists(withId: sourceId) {
try? self.mapView.mapboxMap.style.updateGeoJSONSource(withId: sourceId, geoJSON: .featureCollection(geoJSON))
} else {
// 初回のみソースを追加
self.latitudeLongitudeLines.source.data = .featureCollection(geoJSON)
try? self.mapView.mapboxMap.style.addSource(self.latitudeLongitudeLines.source, id: sourceId)
// 緯度経度線用の LineLayer を追加
var lineLayer = LineLayer(id: self.latitudeLongitudeLines.layerId)
lineLayer.source = sourceId
lineLayer.lineColor = .constant(StyleColor(UIColor.gray))
lineLayer.lineWidth = .constant(1.0)
lineLayer.lineOpacity = .constant(0.5)
try? self.mapView.mapboxMap.style.addLayer(lineLayer)
}
}
}
}
private func clampLatitude(_ latitude: Double) -> Double {
return min(max(latitude, -90.0), 90.0)
}
private func wrapLongitude(_ longitude: Double) -> Double {
var wrapped = longitude.truncatingRemainder(dividingBy: 360.0)
if wrapped > 180.0 {
wrapped -= 360.0
} else if wrapped < -180.0 {
wrapped += 360.0
}
return wrapped
}
private func generateLatitudeLongitudeLinesForVisibleRegion(
minLatitude: Double,
maxLatitude: Double,
minLongitude: Double,
maxLongitude: Double,
latitudeInterval: Double = 5.0,
longitudeInterval: Double = 5.0
) -> FeatureCollection {
var features: [Feature] = []
// 経度が逆転している場合は補正(国際日付変更線をまたぐケースなど)
var correctedMinLongitude = minLongitude
let correctedMaxLongitude = maxLongitude
if correctedMinLongitude > correctedMaxLongitude {
correctedMinLongitude -= 360.0
}
// 緯度の開始位置を調整
let startLatitude = ceil(minLatitude / latitudeInterval) * latitudeInterval
let endLatitude = floor(maxLatitude / latitudeInterval) * latitudeInterval
// 経度の開始位置を調整
let startLongitude = ceil(correctedMinLongitude / longitudeInterval) * longitudeInterval
let endLongitude = floor(correctedMaxLongitude / longitudeInterval) * longitudeInterval
// 緯線を生成
for latitude in stride(from: startLatitude, through: endLatitude, by: latitudeInterval) {
var coordinates: [CLLocationCoordinate2D] = []
var multiLineCoordinates: [[CLLocationCoordinate2D]] = []
for longitude in stride(from: correctedMinLongitude, through: correctedMaxLongitude, by: longitudeInterval / 2) {
let wrappedLongitude = wrapLongitude(longitude)
// 経度が180°を跨ぐ場合、線を分割してMultiLineStringを作成
if !coordinates.isEmpty, abs(wrappedLongitude - coordinates.last!.longitude) > 180.0 {
coordinates.append(CLLocationCoordinate2D(latitude: latitude, longitude: -180.0))
coordinates.append(CLLocationCoordinate2D(latitude: latitude, longitude: 180.0))
multiLineCoordinates.append(coordinates)
coordinates = []
}
coordinates.append(CLLocationCoordinate2D(latitude: latitude, longitude: wrappedLongitude))
}
// 最後の線を追加
if !coordinates.isEmpty {
multiLineCoordinates.append(coordinates)
}
// MultiLineStringとして追加
if !multiLineCoordinates.isEmpty {
let multiLineString = MultiLineString(multiLineCoordinates)
let feature = Feature(geometry: .multiLineString(multiLineString))
features.append(feature)
}
}
// 経線を生成
for longitude in stride(from: startLongitude, through: endLongitude, by: longitudeInterval) {
var coordinates: [CLLocationCoordinate2D] = []
for latitude in stride(from: minLatitude, through: maxLatitude, by: latitudeInterval / 2) {
coordinates.append(CLLocationCoordinate2D(latitude: clampLatitude(latitude), longitude: wrapLongitude(longitude)))
}
let lineString = LineString(coordinates)
let feature = Feature(geometry: .lineString(lineString))
features.append(feature)
}
return FeatureCollection(features: features)
}
//MARK: - 緯度経度ラベル
private func latitudeLongitudeLabelsLayer() {
let visibleRegion = mapView.mapboxMap.coordinateBounds(for: mapView.frame)
let interval = getScaleIntervals()
// GeoJSON生成をバックグラウンドで実行
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
let geoJSON = generateLatitudeLongitudeLabels(
minLatitude: visibleRegion.southwest.latitude,
maxLatitude: visibleRegion.northeast.latitude,
minLongitude: visibleRegion.southwest.longitude,
maxLongitude: visibleRegion.northeast.longitude,
latitudeInterval: interval.latitudeInterval,
longitudeInterval: interval.longitudeInterval
)
// メインスレッドで地図に反映
DispatchQueue.main.async {
let sourceId = self.latitudeLongitudeLabels.sourceId
let source = self.latitudeLongitudeLabels.source
self.latitudeLongitudeLabels.source.data = .featureCollection(geoJSON)
if self.mapView.mapboxMap.style.sourceExists(withId: sourceId) {
try? self.mapView.mapboxMap.style.updateGeoJSONSource(withId: sourceId, geoJSON: .featureCollection(geoJSON))
} else {
try? self.mapView.mapboxMap.style.addSource(source, id: sourceId)
let offsetY = self.mapView.safeAreaInsets.top / 10.0
// Add SymbolLayer for latitude and longitude labels
var symbolLayer = SymbolLayer(id: self.latitudeLongitudeLabels.layerId)
symbolLayer.source = sourceId
symbolLayer.textField = .expression(Exp(.get) { "label" })
symbolLayer.textSize = .constant(12.0)
symbolLayer.textColor = .constant(StyleColor(UIColor.black))
symbolLayer.textAnchor = .constant(.bottomLeft)
symbolLayer.textOffset = .constant([0.5, offsetY])
try? self.mapView.mapboxMap.style.addLayer(symbolLayer)
}
}
}
}
private func generateLatitudeLongitudeLabels(
minLatitude: Double,
maxLatitude: Double,
minLongitude: Double,
maxLongitude: Double,
latitudeInterval: Double = 5.0,
longitudeInterval: Double = 5.0
) -> FeatureCollection {
var features: [Feature] = []
// 経度が逆転している場合は補正
var correctedMinLongitude = minLongitude
let correctedMaxLongitude = maxLongitude
if correctedMinLongitude > correctedMaxLongitude {
correctedMinLongitude -= 360.0
}
// 緯度の開始位置を調整
let startLatitude = ceil(minLatitude / latitudeInterval) * latitudeInterval
let endLatitude = floor(maxLatitude / latitudeInterval) * latitudeInterval
// 経度の開始位置を調整
let startLongitude = ceil(correctedMinLongitude / longitudeInterval) * longitudeInterval
let endLongitude = floor(correctedMaxLongitude / longitudeInterval) * longitudeInterval
// 緯線ラベルを追加(左端に表示)
for latitude in stride(from: startLatitude, through: endLatitude, by: latitudeInterval) {
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: wrapLongitude(correctedMinLongitude))
var feature = Feature(geometry: .point(Point(coordinate)))
feature.properties = ["label": .string("\(Int(latitude))°")]
features.append(feature)
}
// 経線ラベルを追加(上端に表示)
for longitude in stride(from: startLongitude, through: endLongitude, by: longitudeInterval) {
let coordinate = CLLocationCoordinate2D(latitude: clampLatitude(maxLatitude), longitude: wrapLongitude(longitude))
var feature = Feature(geometry: .point(Point(coordinate)))
feature.properties = ["label": .string("\(Int(longitude))°")]
features.append(feature)
}
return FeatureCollection(features: features)
}
/** /**
* 自船シンボル表示レイヤー * 自船シンボル表示レイヤー
*/ */
......
import SwiftUI import SwiftUI
struct LayoutConstants { struct LayoutConstants {
static let menuTitleHeight: CGFloat = 60 //メニュータイトルの高さ static let menuTitleHeight: CGFloat = 50 //メニュータイトルの高さ
static let menuViewWidth: CGFloat = 340 //iPadの地図上に表示されるメニューの幅 static let menuViewWidth: CGFloat = 400 //iPadの地図上に表示されるメニューの幅
static let tabHeight: CGFloat = 50 //タブの高さ static let tabHeight: CGFloat = 50 //タブの高さ
static let menuMinimumHeight: CGFloat = menuTitleHeight + tabHeight static let menuMinimumHeight: CGFloat = menuTitleHeight + tabHeight
......
...@@ -10,56 +10,71 @@ struct CustomTabBar: View { ...@@ -10,56 +10,71 @@ struct CustomTabBar: View {
@ObservedObject var pushHistory = SharingData.pushHistory @ObservedObject var pushHistory = SharingData.pushHistory
var body: some View { var body: some View {
VStack(spacing: 0){
Divider()
HStack(spacing: 0){ ZStack(alignment: .bottomLeading) {
ForEach(Tab.allCases, id: \.rawValue) { tab in if UIDevice.current.userInterfaceIdiom == .pad {
VStack{ RoundedRectangle(cornerRadius: 12)
ZStack(alignment: .bottomTrailing) { .fill(ColorSet.BottomNav.color)
if !SharingData.my.isCommunication && tab == Tab.chat { .frame(width: LayoutConstants.menuViewWidth, height: LayoutConstants.tabHeight)
Image("tab_chat_Invalid") .padding(.leading, 5)
.onTapGesture { }
viewModel.handleTabSelection(
selectedTabModel: selectedTabModel,
tab: tab,
message: message,
history: pushHistory,
location: location,
)
}
} else {
Image(selectedTabModel.activeTab == tab ? tab.rawValue + "_selected" : tab.rawValue)
.onTapGesture {
viewModel.handleTabSelection(
selectedTabModel: selectedTabModel,
tab: tab,
message: message,
history: pushHistory,
location: location,
)
}
}
if tab == Tab.chat { VStack(alignment: UIDevice.current.userInterfaceIdiom == .phone ? .center : .leading, spacing: 0) {
NotificationBadge(viewModel: viewModel.chatBadgeViewModel, font: FontStyle.VersionText.font) Rectangle()
} .fill(Color.clear)
.frame(height: 1)
HStack(spacing: 0){
ForEach(Tab.allCases, id: \.rawValue) { tab in
VStack{
ZStack(alignment: .bottomTrailing) {
if !SharingData.my.isCommunication && tab == Tab.chat {
Image("tab_chat_Invalid")
.onTapGesture {
viewModel.handleTabSelection(
selectedTabModel: selectedTabModel,
tab: tab,
message: message,
history: pushHistory,
location: location,
)
}
} else {
Image(selectedTabModel.activeTab == tab ? tab.rawValue + "_selected" : tab.rawValue)
.onTapGesture {
viewModel.handleTabSelection(
selectedTabModel: selectedTabModel,
tab: tab,
message: message,
history: pushHistory,
location: location,
)
}
}
if tab == Tab.alert { if tab == Tab.chat {
NotificationBadge(viewModel: viewModel.alertBadgeViewModel, font: FontStyle.VersionText.font) NotificationBadge(viewModel: viewModel.chatBadgeViewModel, font: FontStyle.VersionText.font)
}
if tab == Tab.alert {
NotificationBadge(viewModel: viewModel.alertBadgeViewModel, font: FontStyle.VersionText.font)
}
} }
}
Text(tab.title) Text(tab.title)
.font(.caption) .font(.caption)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
} }
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
} }
.frame(
width: UIDevice.current.userInterfaceIdiom == .pad ? LayoutConstants.menuViewWidth : nil,
height: LayoutConstants.tabHeight
)
} }
.frame(height: LayoutConstants.tabHeight)
} }
.background(ColorSet.BottomNav.color) .background(UIDevice.current.userInterfaceIdiom == .phone ? ColorSet.BottomNav.color : Color.clear)
.modifier(ChangeModeAlertModifier( .modifier(ChangeModeAlertModifier(
isPresented: $selectedTabModel.isShowChangeEmrMode, isPresented: $selectedTabModel.isShowChangeEmrMode,
currentMode: message.mode, currentMode: message.mode,
......
//
// MainTabView.swift
// forShip
//
// Created by Mamoru Sugita on 2023/10/13.
//
import SwiftUI import SwiftUI
enum Tab: String, CaseIterable{ enum Tab: String, CaseIterable{
...@@ -26,9 +19,11 @@ enum Tab: String, CaseIterable{ ...@@ -26,9 +19,11 @@ enum Tab: String, CaseIterable{
struct MainTabView: View { struct MainTabView: View {
@EnvironmentObject var selectedTabModel: SelectedTabModel @EnvironmentObject var selectedTabModel: SelectedTabModel
@EnvironmentObject private var sceneDelegate: SceneDelegate @EnvironmentObject private var sceneDelegate: SceneDelegate
@StateObject private var viewModel = ChatInputViewModel()
@ObservedObject var location = SharingData.location @ObservedObject var location = SharingData.location
@State private var offset = CGSize.zero @State private var offset = CGSize.zero
@State var isSignout = false @State var isSignout = false
@State private var isPresentingTextEditorView: Bool = false
var isTabWindowActive: Bool { var isTabWindowActive: Bool {
sceneDelegate.tabWindow != nil sceneDelegate.tabWindow != nil
...@@ -56,9 +51,22 @@ struct MainTabView: View { ...@@ -56,9 +51,22 @@ struct MainTabView: View {
GeometryReader { geometry in GeometryReader { geometry in
ZStack(alignment: .bottom) { ZStack(alignment: .bottom) {
TabView(selection: $selectedTabModel.activeTab) { TabView(selection: $selectedTabModel.activeTab) {
mapTab ZStack {
MapRepresentable()
MapInformation()
.zIndex(1)
.padding(.top, geometry.safeAreaInsets.top)
.padding(.trailing, geometry.safeAreaInsets.trailing + 10)
}
.ignoresSafeArea(edges: .top)
.tag(Tab.map)
if SharingData.my.isCommunication { if SharingData.my.isCommunication {
ChatView(contentWidth: UIScreen.main.bounds.width).tag(Tab.chat) ChatView(
viewModel: viewModel,
isPresentingText: $isPresentingTextEditorView,
contentWidth: UIScreen.main.bounds.width
).tag(Tab.chat)
} }
NotificationView().tag(Tab.alert) NotificationView().tag(Tab.alert)
MenuView(isSignout: $isSignout).tag(Tab.menu) MenuView(isSignout: $isSignout).tag(Tab.menu)
...@@ -83,28 +91,24 @@ struct MainTabView: View { ...@@ -83,28 +91,24 @@ struct MainTabView: View {
enum OverlayHeightMode: CaseIterable { enum OverlayHeightMode: CaseIterable {
case titleOnly case titleOnly
// case half
case full case full
var heightRatio: CGFloat {
switch self {
case .titleOnly: return 0.15
// case .half: return 0.5
case .full: return 1.0
}
}
} }
@State private var overlayHeightMode: OverlayHeightMode = .full @State private var overlayHeightMode: OverlayHeightMode = .titleOnly
private var padView: some View { private var padView: some View {
GeometryReader { geometry in GeometryReader { geometry in
let overlayWidth = LayoutConstants.menuViewWidth let overlayWidth = LayoutConstants.menuViewWidth
let fullHeight = geometry.size.height - (LayoutConstants.menuTitleHeight + geometry.safeAreaInsets.top + 15) let fullHeight = geometry.size.height - (LayoutConstants.menuTitleHeight + geometry.safeAreaInsets.top + 15)
let overlayHeight = fullHeight * overlayHeightMode.heightRatio let titleOnlyHeight = LayoutConstants.menuTitleHeight + geometry.safeAreaInsets.top
let overlayHeight = overlayHeightMode == .full ? fullHeight : titleOnlyHeight
ZStack(alignment: .bottomLeading) { ZStack(alignment: .bottomLeading) {
MapRepresentable() MapRepresentable()
.ignoresSafeArea() .ignoresSafeArea()
MapInformation()
.zIndex(1)
.padding(.top, geometry.safeAreaInsets.top)
.padding(.trailing, geometry.safeAreaInsets.trailing + 10)
if selectedTabModel.activeTab == .menu { if selectedTabModel.activeTab == .menu {
MenuView(isSignout: $isSignout) MenuView(isSignout: $isSignout)
...@@ -137,8 +141,11 @@ struct MainTabView: View { ...@@ -137,8 +141,11 @@ struct MainTabView: View {
MenuTaskView() MenuTaskView()
.tag(Tab.map) .tag(Tab.map)
if SharingData.my.isCommunication { if SharingData.my.isCommunication {
ChatView(contentWidth: overlayWidth) ChatView(
.tag(Tab.chat) viewModel: viewModel,
isPresentingText: $isPresentingTextEditorView,
contentWidth: overlayWidth
).tag(Tab.chat)
} }
NotificationView() NotificationView()
.tag(Tab.alert) .tag(Tab.alert)
...@@ -149,21 +156,28 @@ struct MainTabView: View { ...@@ -149,21 +156,28 @@ struct MainTabView: View {
.background(ColorSet.BackgroundPrimary.color) .background(ColorSet.BackgroundPrimary.color)
.cornerRadius(12) .cornerRadius(12)
.shadow(radius: 4) .shadow(radius: 4)
.padding(.bottom, LayoutConstants.menuTitleHeight + 5) .padding(.bottom, LayoutConstants.tabHeight + 5)
.padding(.leading, 5) .padding(.leading, 5)
} }
}
}
}
@ViewBuilder // MARK: - 別ViewとしてのTextEditor (iPad)
private var mapTab: some View { if isPresentingTextEditorView {
ZStack { VStack {
MapRepresentable() TextEditorView(
MapInformation().zIndex(1) inputText: $viewModel.inputText,
isPresenting: $isPresentingTextEditorView,
onSend: {
viewModel.sendChatMessage()
isPresentingTextEditorView = false
}
)
.transparentBackground()
.frame(maxWidth: geometry.size.width * 1.2, maxHeight: 300)
}
.zIndex(3)
}
}
} }
.ignoresSafeArea(edges: .top)
.tag(Tab.map)
} }
private func configureTabBarAppearance() { private func configureTabBarAppearance() {
...@@ -173,9 +187,3 @@ struct MainTabView: View { ...@@ -173,9 +187,3 @@ struct MainTabView: View {
UITabBar.appearance().standardAppearance = appearance UITabBar.appearance().standardAppearance = appearance
} }
} }
#Preview {
MainTabView()
.environmentObject(SelectedTabModel())
.environmentObject(SceneDelegate())
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment