Commit cbac0b93 authored by shigemi miura's avatar shigemi miura

iPad用テキスト入力画面

緯度経度表示
parent f588da6c
......@@ -63,6 +63,8 @@
02CE4DDA2ADFBA72002E79BC /* MapRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CE4DD92ADFBA72002E79BC /* MapRepresentable.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 */; };
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 */; };
D5056D482E925FE90076AC88 /* LayoutConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D472E925FDF0076AC88 /* LayoutConstants.swift */; };
D5056D522E9343300076AC88 /* TaskTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D512E9343270076AC88 /* TaskTitleView.swift */; };
......@@ -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>"; };
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>"; };
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>"; };
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>"; };
......@@ -673,6 +677,8 @@
02A1DE2D2AFB497B005BCF55 /* View */ = {
isa = PBXGroup;
children = (
D50533FB2EA8A93A00975ADD /* TextEditorView.swift */,
D50533F92EA7955500975ADD /* TransparentBackgroundViewModifier.swift */,
020B98622ADD14E40029DE4C /* ChatView.swift */,
02A1DE2E2AFB4AA0005BCF55 /* ChatInputView.swift */,
02C3E5D02AFCC16800AF7837 /* ChatTitleView.swift */,
......@@ -1111,6 +1117,7 @@
025C28002B034A1900BADC49 /* PDFViewer.swift in Sources */,
02C3E6092AFDF30000AF7837 /* ChatMemberView.swift in Sources */,
020B98412AD8C3810029DE4C /* LoginView.swift in Sources */,
D50533FC2EA8A93D00975ADD /* TextEditorView.swift in Sources */,
D5AE351D2AEBA6FC00059889 /* SessionLogin.swift in Sources */,
D5AF8A522E89252400BECA22 /* MenuMainView.swift in Sources */,
D5258C9F2B03527400365276 /* ResGetMessages.swift in Sources */,
......@@ -1200,6 +1207,7 @@
02CE4D892ADF62E1002E79BC /* EcaSettingView.swift in Sources */,
025F99722B2AE3AF00C9A18A /* TaskViewModel.swift in Sources */,
D54D17512B35923400A0EAA5 /* GetManualUrl.swift in Sources */,
D50533FA2EA7956900975ADD /* TransparentBackgroundViewModifier.swift in Sources */,
D5258CA32B036CC500365276 /* SessionGetMessage.swift in Sources */,
D52C2C082B9195A8003B286C /* MenuInformationView.swift in Sources */,
D5258C992B0334BF00365276 /* SessionShipStatus.swift in Sources */,
......
......@@ -4,18 +4,31 @@ import Speech
struct ChatInputView: View {
@EnvironmentObject private var sceneDelegate: SceneDelegate
@StateObject private var speechRecognizer = SpeechRecognizer()
@StateObject private var viewModel: ChatInputViewModel
@State private var imageData: Data = .init(capacity: 0)
@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 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._isUploadingDialogPresented = isUploadingDialogPresented
self._viewModel = StateObject(wrappedValue: ChatInputViewModel())
self._isPresentingText = isPresentingText
}
private var isIpad: Bool {
UIDevice.current.userInterfaceIdiom == .pad
}
var body: some View {
......@@ -31,6 +44,7 @@ struct ChatInputView: View {
sceneDelegate.tabWindow?.isHidden = true
source = .camera
viewModel.handleMediaInput(type: .camera)
isPresentingText = false
} label: {
Image(systemName: "camera")
.resizable()
......@@ -44,6 +58,7 @@ struct ChatInputView: View {
sceneDelegate.tabWindow?.isHidden = true
source = .photoLibrary
viewModel.handleMediaInput(type: .photoLibrary)
isPresentingText = false
} label: {
Label("Photo Library", systemImage: "photo.on.rectangle")
}
......@@ -67,6 +82,9 @@ struct ChatInputView: View {
if viewModel.isRecording {
speechRecognizer.transcribedText = ""
speechRecognizer.startRecording()
if isIpad {
isPresentingText = true
}
} else {
speechRecognizer.stopRecording()
viewModel.inputText = speechRecognizer.transcribedText
......@@ -78,7 +96,17 @@ struct ChatInputView: View {
.padding(5)
}
//MARK: - テキスト入力
//MARK: - テキスト入力開始ボタン
if isIpad {
Button {
isPresentingText = true
} label: {
Image(systemName: isPresentingText ? "message.fill" : "message")
.resizable()
.frame(width: 20, height: 20)
.padding(5)
}
} else {
TextEditor(text: $viewModel.inputText)
.focused($isKeyboardFocused)
.font(FontStyle.DefaultText.font)
......@@ -111,8 +139,10 @@ struct ChatInputView: View {
.onChange(of: viewModel.isKeyboardFocused) { isFocused in
isKeyboardFocused = isFocused
}
}
//MARK: - 送信ボタン
if !isIpad {
Button {
viewModel.sendChatMessage()
} label: {
......@@ -126,6 +156,7 @@ struct ChatInputView: View {
.padding(.trailing, 11)
.disabled(viewModel.inputText.isEmpty)
}
}
.background(ColorSet.BackgroundSecondary.color)
// MARK: - 画像ピッカー
......@@ -225,8 +256,3 @@ struct ChatInputView: View {
}
}
}
#Preview {
ChatInputView(isFocus: .constant(false), isUploadingDialogPresented: .constant(false))
.environmentObject(SceneDelegate())
}
......@@ -16,6 +16,8 @@ struct ChatView: View {
@State private var totalMediaCount: Int = 0
@State private var isMediaLoading: Bool = false
@State private var isUploadingDialogPresented: Bool = false
@ObservedObject var viewModel: ChatInputViewModel
@Binding var isPresentingText: Bool
var contentWidth: CGFloat
var body: some View {
......@@ -51,7 +53,10 @@ struct ChatView: View {
.padding(.top)
.onAppear {
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
count + (msg.type >= 0 ? 1 : 0)
......@@ -129,16 +134,21 @@ struct ChatView: View {
message.viewCnt = 0
}
}
ChatInputView(isFocus: $isFocus, isUploadingDialogPresented: $isUploadingDialogPresented)
ChatInputView(
viewModel: viewModel,
isFocus: $isFocus,
isUploadingDialogPresented: $isUploadingDialogPresented,
isPresentingText: $isPresentingText
)
}
if !isMediaLoading {
LoadingView()
}
if isUploadingDialogPresented {
// if isUploadingDialogPresented {
// UpLoadingView()
}
// }
}
.background(ColorSet.BackgroundPrimary.color)
}
......@@ -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 {
var body: some View {
VStack {
VStack {
VStack(alignment: .trailing, spacing: 5) {
Text(presentLocation())
.font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.foregroundColor(Color.black)
.padding(5)
Text("Heading:" + String(ship.heading) + "[°]")
.font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.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(10)
.background(Color.white.opacity(0.7))
.cornerRadius(3)
}
.padding(.top, 100)
.padding(.trailing, positionLocation())
VStack {
ForEach(ngaData.ngaArea.map{ $0.1 }.filter{ $0.passingCnt > 0 }, id: \.name) { nga in
Text("Entering NGA \(nga.name)")
......@@ -35,8 +43,10 @@ struct MapInformation: View {
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
}
//MARK: - 地図上の船情報
func presentLocation() -> String {
var location = " 00°00.000'N\n000°00.000'E"
if let shipPos = ship.location {
......@@ -44,18 +54,8 @@ struct MapInformation: View {
}
return location
}
func positionLocation() -> CGFloat {
var yPos: CGFloat = 200
if UIDevice.current.userInterfaceIdiom == .pad {
yPos = 900
}
return yPos
}
}
#Preview {
MapInformation()
}
......@@ -110,20 +110,27 @@ class MapViewController : UIViewController {
internal var mapView: MapView!
internal var touchedFeatures: Feature? = nil
private var lastVisibleRegion: CoordinateBounds?
private var previousZoomLevel: Double = 2.0
override func viewDidLoad() {
super.viewDidLoad()
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))
mapView = MapView(frame: view.bounds, mapInitOptions: options)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(mapView)
mapView.mapboxMap.onNext(event: .mapLoaded) { [self] _ in
self.addIconImage()
self.addLayers()
self.latitudeLongitudeLinesLayer()
self.latitudeLongitudeLabelsLayer()
mapView.mapboxMap.onEvery(event: .cameraChanged) { _ in
self.updateOnViewportOrZoomChange()
}
}
//MARK: - 地図上クリック
......@@ -255,6 +262,8 @@ class MapViewController : UIViewController {
case AlertAreaSymbol
case AlertAreaLine
case AlertAreaFill
case LatitudeLongitudeLines
case LatitudeLongitudeLabels
}
var ownShipSymbol = MapSource(layer: .OwnShipSymbol)
......@@ -273,6 +282,8 @@ class MapViewController : UIViewController {
var alertAreaFill = MapSource(layer: .AlertAreaFill)
var alertAreaLine = MapSource(layer: .AlertAreaLine)
var alertAreaSymbol = MapSource(layer: .AlertAreaSymbol)
var latitudeLongitudeLines = MapSource(layer: .LatitudeLongitudeLines)
var latitudeLongitudeLabels = MapSource(layer: .LatitudeLongitudeLabels)
/**
* レイヤー作成
......@@ -294,6 +305,289 @@ class MapViewController : UIViewController {
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
struct LayoutConstants {
static let menuTitleHeight: CGFloat = 60 //メニュータイトルの高さ
static let menuViewWidth: CGFloat = 340 //iPadの地図上に表示されるメニューの幅
static let menuTitleHeight: CGFloat = 50 //メニュータイトルの高さ
static let menuViewWidth: CGFloat = 400 //iPadの地図上に表示されるメニューの幅
static let tabHeight: CGFloat = 50 //タブの高さ
static let menuMinimumHeight: CGFloat = menuTitleHeight + tabHeight
......
......@@ -10,8 +10,19 @@ struct CustomTabBar: View {
@ObservedObject var pushHistory = SharingData.pushHistory
var body: some View {
VStack(spacing: 0){
Divider()
ZStack(alignment: .bottomLeading) {
if UIDevice.current.userInterfaceIdiom == .pad {
RoundedRectangle(cornerRadius: 12)
.fill(ColorSet.BottomNav.color)
.frame(width: LayoutConstants.menuViewWidth, height: LayoutConstants.tabHeight)
.padding(.leading, 5)
}
VStack(alignment: UIDevice.current.userInterfaceIdiom == .phone ? .center : .leading, spacing: 0) {
Rectangle()
.fill(Color.clear)
.frame(height: 1)
HStack(spacing: 0){
ForEach(Tab.allCases, id: \.rawValue) { tab in
......@@ -57,9 +68,13 @@ struct CustomTabBar: View {
.contentShape(Rectangle())
}
}
.frame(height: LayoutConstants.tabHeight)
.frame(
width: UIDevice.current.userInterfaceIdiom == .pad ? LayoutConstants.menuViewWidth : nil,
height: LayoutConstants.tabHeight
)
}
}
.background(ColorSet.BottomNav.color)
.background(UIDevice.current.userInterfaceIdiom == .phone ? ColorSet.BottomNav.color : Color.clear)
.modifier(ChangeModeAlertModifier(
isPresented: $selectedTabModel.isShowChangeEmrMode,
currentMode: message.mode,
......
//
// MainTabView.swift
// forShip
//
// Created by Mamoru Sugita on 2023/10/13.
//
import SwiftUI
enum Tab: String, CaseIterable{
......@@ -26,9 +19,11 @@ enum Tab: String, CaseIterable{
struct MainTabView: View {
@EnvironmentObject var selectedTabModel: SelectedTabModel
@EnvironmentObject private var sceneDelegate: SceneDelegate
@StateObject private var viewModel = ChatInputViewModel()
@ObservedObject var location = SharingData.location
@State private var offset = CGSize.zero
@State var isSignout = false
@State private var isPresentingTextEditorView: Bool = false
var isTabWindowActive: Bool {
sceneDelegate.tabWindow != nil
......@@ -56,9 +51,22 @@ struct MainTabView: View {
GeometryReader { geometry in
ZStack(alignment: .bottom) {
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 {
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)
MenuView(isSignout: $isSignout).tag(Tab.menu)
......@@ -83,28 +91,24 @@ struct MainTabView: View {
enum OverlayHeightMode: CaseIterable {
case titleOnly
// case half
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 {
GeometryReader { geometry in
let overlayWidth = LayoutConstants.menuViewWidth
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) {
MapRepresentable()
.ignoresSafeArea()
MapInformation()
.zIndex(1)
.padding(.top, geometry.safeAreaInsets.top)
.padding(.trailing, geometry.safeAreaInsets.trailing + 10)
if selectedTabModel.activeTab == .menu {
MenuView(isSignout: $isSignout)
......@@ -137,8 +141,11 @@ struct MainTabView: View {
MenuTaskView()
.tag(Tab.map)
if SharingData.my.isCommunication {
ChatView(contentWidth: overlayWidth)
.tag(Tab.chat)
ChatView(
viewModel: viewModel,
isPresentingText: $isPresentingTextEditorView,
contentWidth: overlayWidth
).tag(Tab.chat)
}
NotificationView()
.tag(Tab.alert)
......@@ -149,21 +156,28 @@ struct MainTabView: View {
.background(ColorSet.BackgroundPrimary.color)
.cornerRadius(12)
.shadow(radius: 4)
.padding(.bottom, LayoutConstants.menuTitleHeight + 5)
.padding(.bottom, LayoutConstants.tabHeight + 5)
.padding(.leading, 5)
}
// MARK: - 別ViewとしてのTextEditor (iPad)
if isPresentingTextEditorView {
VStack {
TextEditorView(
inputText: $viewModel.inputText,
isPresenting: $isPresentingTextEditorView,
onSend: {
viewModel.sendChatMessage()
isPresentingTextEditorView = false
}
)
.transparentBackground()
.frame(maxWidth: geometry.size.width * 1.2, maxHeight: 300)
}
.zIndex(3)
}
}
@ViewBuilder
private var mapTab: some View {
ZStack {
MapRepresentable()
MapInformation().zIndex(1)
}
.ignoresSafeArea(edges: .top)
.tag(Tab.map)
}
private func configureTabBarAppearance() {
......@@ -173,9 +187,3 @@ struct MainTabView: View {
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