Anonymous
Необъяснимый белый фон под SwiftUI Webview
Сообщение
Anonymous » 17 фев 2026, 19:56
Я работаю над парой проектов Swift, и один из них, работающий на iPad, отказывается сотрудничать с этим веб-просмотром. На изображении ниже показано пробел под редактором. Это появляется только тогда, когда пользователь прокручивает содержимое редактора вниз. Для контекста редактор фиксирует высоту тела на уровне 100 vh в альбомном режиме, чтобы редактор мог отображаться в контейнере с возможностью прокрутки рядом, и этот пробел не отображается, когда iPad находится в портретном режиме со скрытым редактором.
Кроме того, Я включил режим vim в редакторе, и это происходит чаще, почти всегда, когда редактор находится в режиме vim и пользователь переходит к нижней части содержимого заметки.
Это мой веб-просмотр:
Код: Выделить всё
struct MarkdownTabView: View {
@Binding var editingNote: NoteModel?
@Binding var fullScreenCover: MainFullScreenCover?
var onNavigateToNote: (NoteModel) -> Void = { _ in }
@AppStorage(AppStorageKeys.editorThemeDark.rawValue) private
var editorThemeDark: CodeSyntaxTheme = .dracula
@AppStorage(AppStorageKeys.editorThemeLight.rawValue) private
var editorThemeLight: CodeSyntaxTheme = .githubLight
@AppStorage(AppStorageKeys.theme.rawValue) private var theme: WebViewTheme =
.fluster
@AppStorage(AppStorageKeys.editorKeymap.rawValue) private var editorKeymap: EditorKeymap = .base
@AppStorage(AppStorageKeys.hasLaunchedPreviously.rawValue) private
var hasPreviouslyLaunched: Bool = false
@Environment(ThemeManager.self) private var themeManager: ThemeManager
var editorContainer: MdxEditorWebviewContainer
init(
editingNote: Binding, editorContainer: MdxEditorWebviewContainer,
onNavigateToNote: ((NoteModel) -> Void)?,
fullScreenCover: Binding
) {
self._editingNote = editingNote
self._fullScreenCover = fullScreenCover
self.editorContainer = editorContainer
if onNavigateToNote != nil {
self.onNavigateToNote = onNavigateToNote!
}
}
var body: some View {
if let editingNoteBinding = Binding($editingNote) {
NavigationStack {
MdxEditorWebview(
url:
Bundle.main.url(
forResource: "index",
withExtension: "html",
subdirectory: "splitview_mdx_editor"
)!,
theme: $theme,
editorThemeDark: $editorThemeDark,
editorThemeLight: $editorThemeLight,
editingNote: editingNoteBinding,
editorKeymap: $editorKeymap,
container: editorContainer,
onNavigateToNote: onNavigateToNote,
fullScreenCover: $fullScreenCover
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
// .frame(width: geo.size.width, height: geo.size.height, alignment: .topLeading)
// TODO: Remove this. This is just for easy development.
.onAppear {
if let parsedMdx = editingNote?.markdown
.preParsedBody
{
editorContainer.setParsedEditorContentString(
content: parsedMdx
)
}
UIScrollView.appearance().bounces = false
UIScrollView.appearance().isScrollEnabled =
false
}
.onDisappear {
UIScrollView.appearance().bounces = true
UIScrollView.appearance().isScrollEnabled = true
}
}
} else {
if hasPreviouslyLaunched {
SelectNoteToContinueView()
} else {
ProgressView()
.progressViewStyle(.circular)
.scaleEffect(1.5)
.tint(themeManager.theme.primary)
}
}
}
}
И MdxEditorWebview:
Код: Выделить всё
@MainActor
public struct MdxEditorWebviewInternal: UIViewRepresentable {
@State private var webView: WKWebView = WKWebView(
frame: .zero,
configuration: getConfig()
)
@State private var didSetInitialContent = false
@State var haveSetInitialContent: Bool = false
@Environment(\.openURL) var openURL
@Environment(\.modelContext) var modelContext
@Environment(\.colorScheme) var colorScheme
@Environment(\.createDataHandler) var dataHandler
@AppStorage(AppStorageKeys.webviewFontSize.rawValue) private
var webviewFontSize: WebviewFontSize = .base
let url: URL
@Binding var show: Bool
@Binding var theme: WebViewTheme
@Binding var editorThemeDark: CodeSyntaxTheme
@Binding var editorThemeLight: CodeSyntaxTheme
@Binding var editingNote: NoteModel
@Binding var editorKeymap: EditorKeymap
@Binding var fullScreenCover: MainFullScreenCover?
var onNavigateToNote: (NoteModel) -> Void
let container: MdxEditorWebviewContainer
public init(
url: URL,
theme: Binding,
editorThemeDark: Binding,
editorThemeLight: Binding,
editingNote: Binding,
editorKeymap: Binding,
container: MdxEditorWebviewContainer,
show: Binding,
onNavigateToNote: @escaping (NoteModel) -> Void,
fullScreenCover: Binding
) {
self.url = url
self._theme = theme
self._editorThemeDark = editorThemeDark
self._editorThemeLight = editorThemeLight
self._editingNote = editingNote
self._editorKeymap = editorKeymap
self._fullScreenCover = fullScreenCover
self.container = container
self._show = show
self.onNavigateToNote = onNavigateToNote
}
public func makeUIView(context: Context) -> WKWebView {
let webView = container.webView
webView.isHidden = true
webView.navigationDelegate = context.coordinator
let editorContentControllers = [
SplitviewEditorWebviewActions.setWebviewLoaded.rawValue,
SplitviewEditorWebviewActions.onEditorChange.rawValue,
SplitviewEditorWebviewActions.requestSplitviewEditorData.rawValue,
SplitviewEditorWebviewActions.requestParsedMdxContent.rawValue,
SplitviewEditorWebviewActions.onTagClick.rawValue,
MdxPreviewWebviewActions.viewNoteByUserDefinedId.rawValue,
MdxPreviewWebviewActions.requestNoteData.rawValue
]
if colorScheme == .dark {
webView.evaluateJavaScript(
"""
document.body.classList.add("dark"); null;
"""
)
}
for controllerName in editorContentControllers {
addUserContentController(
controller: webView.configuration.userContentController,
coordinator: context.coordinator,
name: controllerName
)
}
// Loading the page only once
webView.loadFileURL(url, allowingReadAccessTo: url)
if colorScheme == .dark {
webView.evaluateJavaScript(
"""
document.body.classList.add("dark"); null;
"""
)
}
return webView
}
public func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.isHidden = !show
// uiView.scrollView.contentInset = .zero
// uiView.scrollView.scrollIndicatorInsets = .zero
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
public func setInitialProperties() {
container.setInitialProperties(
editingNote: editingNote,
codeEditorTheme: colorScheme == .dark
? editorThemeDark : editorThemeLight,
editorKeymap: editorKeymap,
theme: theme,
fontSize: webviewFontSize,
editorThemeDark: editorThemeDark,
editorThemeLight: editorThemeLight,
darkMode: colorScheme == .dark,
modelContext: modelContext
)
}
public func setInitialContent() {
let s = editingNote.markdown.body.toQuotedJavascriptString() ?? "''"
container.runJavascript(
"""
window.localStorage.setItem("\(SplitviewEditorWebviewLocalStorageKeys.initialValue.rawValue)", \(s))
window.setEditorContent(\(s))
"""
)
}
public func handleViewNoteByUserDefinedId(id: String) {
print("Here...")
let fetchDescriptor = FetchDescriptor(
predicate: #Predicate { note in
note.frontMatter.userDefinedId == id
})
if let notes = try? self.modelContext.fetch(fetchDescriptor) {
if !notes.isEmpty {
let note = notes.first
self.editingNote = note!
self.onNavigateToNote(note!)
}
}
}
public func handleTagClick(tagBody: String) {
let fetchDescriptor = FetchDescriptor(
predicate: #Predicate { t in
t.value == tagBody
})
if let tags = try? modelContext.fetch(fetchDescriptor) {
if !tags.isEmpty {
fullScreenCover = .tagSearch(tag: tags.first!)
}
}
}
}
public struct MdxEditorWebview: View {
@State private var show: Bool = false
@State private var showEditNoteTaggables: Bool = false
@Environment(ThemeManager.self) private var themeManager: ThemeManager
let url: URL
@Binding var theme: WebViewTheme
@Binding var editorThemeDark: CodeSyntaxTheme
@Binding var editorThemeLight: CodeSyntaxTheme
@Binding var editingNote: NoteModel
@Binding var editorKeymap: EditorKeymap
@Binding var fullScreenCover: MainFullScreenCover?
var onNavigateToNote: (NoteModel) -> Void
let container: MdxEditorWebviewContainer
public init(
url: URL,
theme: Binding,
editorThemeDark: Binding,
editorThemeLight: Binding,
editingNote: Binding,
editorKeymap: Binding,
container: MdxEditorWebviewContainer,
onNavigateToNote: @escaping (NoteModel) -> Void,
fullScreenCover: Binding?
) {
self.url = url
self._theme = theme
self._editorThemeDark = editorThemeDark
self._editorThemeLight = editorThemeLight
self._editingNote = editingNote
self._editorKeymap = editorKeymap
self.container = container
self.onNavigateToNote = onNavigateToNote
if let fs = fullScreenCover {
self._fullScreenCover = fs
} else {
self._fullScreenCover = .constant(nil)
}
self.onNavigateToNote = onNavigateToNote
}
public var body: some View {
ZStack(alignment: show ? .bottomTrailing : .center) {
MdxEditorWebviewInternal(
url: url,
theme: $theme,
editorThemeDark: $editorThemeDark,
editorThemeLight: $editorThemeLight,
editingNote: $editingNote,
editorKeymap: $editorKeymap,
container: container,
show: $show,
onNavigateToNote: onNavigateToNote,
fullScreenCover: $fullScreenCover,
)
.disableAnimations()
.frame(
alignment: .bottom
)
.scrollDisabled(true)
if !show {
ProgressView()
.progressViewStyle(.circular)
.scaleEffect(1.5)
.tint(themeManager.theme.primary)
} else {
FloatingButtonView(
buttons: [
FloatingButtonItem(
id: "addTaggable",
systemImage: "tag.fill",
action: {
withAnimation {
showEditNoteTaggables.toggle()
}
}
),
FloatingButtonItem(
id: "toggleBookmarked",
systemImage: editingNote.bookmarked ? "bookmark.fill" : "bookmark",
action: {
editingNote.bookmarked.toggle()
}
)
]
)
.padding()
}
}
.fullScreenCover(
isPresented: $showEditNoteTaggables,
content: {
EditNoteTaggablesView(
editingNote: $editingNote,
open: $showEditNoteTaggables
)
},
)
}
func onLoad() async {
}
}
#endif
Заранее благодарим вас за любые предложения. Я работаю со Swift всего пару месяцев, поэтому мне еще есть чему поучиться.
Подробнее здесь:
https://stackoverflow.com/questions/798 ... ui-webview
1771347411
Anonymous
Я работаю над парой проектов Swift, и один из них, работающий на iPad, отказывается сотрудничать с этим веб-просмотром. На изображении ниже показано пробел под редактором. Это появляется только тогда, когда пользователь прокручивает содержимое редактора вниз. Для контекста редактор фиксирует высоту тела на уровне 100 vh в альбомном режиме, чтобы редактор мог отображаться в контейнере с возможностью прокрутки рядом, и этот пробел не отображается, когда iPad находится в портретном режиме со скрытым редактором. [img]https://i.sstatic.net/oTkreVnA.png[/img] Кроме того, Я включил режим vim в редакторе, и это происходит чаще, почти всегда, когда редактор находится в режиме vim и пользователь переходит к нижней части содержимого заметки. Это мой веб-просмотр: [code]struct MarkdownTabView: View { @Binding var editingNote: NoteModel? @Binding var fullScreenCover: MainFullScreenCover? var onNavigateToNote: (NoteModel) -> Void = { _ in } @AppStorage(AppStorageKeys.editorThemeDark.rawValue) private var editorThemeDark: CodeSyntaxTheme = .dracula @AppStorage(AppStorageKeys.editorThemeLight.rawValue) private var editorThemeLight: CodeSyntaxTheme = .githubLight @AppStorage(AppStorageKeys.theme.rawValue) private var theme: WebViewTheme = .fluster @AppStorage(AppStorageKeys.editorKeymap.rawValue) private var editorKeymap: EditorKeymap = .base @AppStorage(AppStorageKeys.hasLaunchedPreviously.rawValue) private var hasPreviouslyLaunched: Bool = false @Environment(ThemeManager.self) private var themeManager: ThemeManager var editorContainer: MdxEditorWebviewContainer init( editingNote: Binding, editorContainer: MdxEditorWebviewContainer, onNavigateToNote: ((NoteModel) -> Void)?, fullScreenCover: Binding ) { self._editingNote = editingNote self._fullScreenCover = fullScreenCover self.editorContainer = editorContainer if onNavigateToNote != nil { self.onNavigateToNote = onNavigateToNote! } } var body: some View { if let editingNoteBinding = Binding($editingNote) { NavigationStack { MdxEditorWebview( url: Bundle.main.url( forResource: "index", withExtension: "html", subdirectory: "splitview_mdx_editor" )!, theme: $theme, editorThemeDark: $editorThemeDark, editorThemeLight: $editorThemeLight, editingNote: editingNoteBinding, editorKeymap: $editorKeymap, container: editorContainer, onNavigateToNote: onNavigateToNote, fullScreenCover: $fullScreenCover ) .frame(maxWidth: .infinity, maxHeight: .infinity) // .frame(width: geo.size.width, height: geo.size.height, alignment: .topLeading) // TODO: Remove this. This is just for easy development. .onAppear { if let parsedMdx = editingNote?.markdown .preParsedBody { editorContainer.setParsedEditorContentString( content: parsedMdx ) } UIScrollView.appearance().bounces = false UIScrollView.appearance().isScrollEnabled = false } .onDisappear { UIScrollView.appearance().bounces = true UIScrollView.appearance().isScrollEnabled = true } } } else { if hasPreviouslyLaunched { SelectNoteToContinueView() } else { ProgressView() .progressViewStyle(.circular) .scaleEffect(1.5) .tint(themeManager.theme.primary) } } } } [/code] И MdxEditorWebview: [code] @MainActor public struct MdxEditorWebviewInternal: UIViewRepresentable { @State private var webView: WKWebView = WKWebView( frame: .zero, configuration: getConfig() ) @State private var didSetInitialContent = false @State var haveSetInitialContent: Bool = false @Environment(\.openURL) var openURL @Environment(\.modelContext) var modelContext @Environment(\.colorScheme) var colorScheme @Environment(\.createDataHandler) var dataHandler @AppStorage(AppStorageKeys.webviewFontSize.rawValue) private var webviewFontSize: WebviewFontSize = .base let url: URL @Binding var show: Bool @Binding var theme: WebViewTheme @Binding var editorThemeDark: CodeSyntaxTheme @Binding var editorThemeLight: CodeSyntaxTheme @Binding var editingNote: NoteModel @Binding var editorKeymap: EditorKeymap @Binding var fullScreenCover: MainFullScreenCover? var onNavigateToNote: (NoteModel) -> Void let container: MdxEditorWebviewContainer public init( url: URL, theme: Binding, editorThemeDark: Binding, editorThemeLight: Binding, editingNote: Binding, editorKeymap: Binding, container: MdxEditorWebviewContainer, show: Binding, onNavigateToNote: @escaping (NoteModel) -> Void, fullScreenCover: Binding ) { self.url = url self._theme = theme self._editorThemeDark = editorThemeDark self._editorThemeLight = editorThemeLight self._editingNote = editingNote self._editorKeymap = editorKeymap self._fullScreenCover = fullScreenCover self.container = container self._show = show self.onNavigateToNote = onNavigateToNote } public func makeUIView(context: Context) -> WKWebView { let webView = container.webView webView.isHidden = true webView.navigationDelegate = context.coordinator let editorContentControllers = [ SplitviewEditorWebviewActions.setWebviewLoaded.rawValue, SplitviewEditorWebviewActions.onEditorChange.rawValue, SplitviewEditorWebviewActions.requestSplitviewEditorData.rawValue, SplitviewEditorWebviewActions.requestParsedMdxContent.rawValue, SplitviewEditorWebviewActions.onTagClick.rawValue, MdxPreviewWebviewActions.viewNoteByUserDefinedId.rawValue, MdxPreviewWebviewActions.requestNoteData.rawValue ] if colorScheme == .dark { webView.evaluateJavaScript( """ document.body.classList.add("dark"); null; """ ) } for controllerName in editorContentControllers { addUserContentController( controller: webView.configuration.userContentController, coordinator: context.coordinator, name: controllerName ) } // Loading the page only once webView.loadFileURL(url, allowingReadAccessTo: url) if colorScheme == .dark { webView.evaluateJavaScript( """ document.body.classList.add("dark"); null; """ ) } return webView } public func updateUIView(_ uiView: WKWebView, context: Context) { uiView.isHidden = !show // uiView.scrollView.contentInset = .zero // uiView.scrollView.scrollIndicatorInsets = .zero } public func makeCoordinator() -> Coordinator { Coordinator(self) } public func setInitialProperties() { container.setInitialProperties( editingNote: editingNote, codeEditorTheme: colorScheme == .dark ? editorThemeDark : editorThemeLight, editorKeymap: editorKeymap, theme: theme, fontSize: webviewFontSize, editorThemeDark: editorThemeDark, editorThemeLight: editorThemeLight, darkMode: colorScheme == .dark, modelContext: modelContext ) } public func setInitialContent() { let s = editingNote.markdown.body.toQuotedJavascriptString() ?? "''" container.runJavascript( """ window.localStorage.setItem("\(SplitviewEditorWebviewLocalStorageKeys.initialValue.rawValue)", \(s)) window.setEditorContent(\(s)) """ ) } public func handleViewNoteByUserDefinedId(id: String) { print("Here...") let fetchDescriptor = FetchDescriptor( predicate: #Predicate { note in note.frontMatter.userDefinedId == id }) if let notes = try? self.modelContext.fetch(fetchDescriptor) { if !notes.isEmpty { let note = notes.first self.editingNote = note! self.onNavigateToNote(note!) } } } public func handleTagClick(tagBody: String) { let fetchDescriptor = FetchDescriptor( predicate: #Predicate { t in t.value == tagBody }) if let tags = try? modelContext.fetch(fetchDescriptor) { if !tags.isEmpty { fullScreenCover = .tagSearch(tag: tags.first!) } } } } public struct MdxEditorWebview: View { @State private var show: Bool = false @State private var showEditNoteTaggables: Bool = false @Environment(ThemeManager.self) private var themeManager: ThemeManager let url: URL @Binding var theme: WebViewTheme @Binding var editorThemeDark: CodeSyntaxTheme @Binding var editorThemeLight: CodeSyntaxTheme @Binding var editingNote: NoteModel @Binding var editorKeymap: EditorKeymap @Binding var fullScreenCover: MainFullScreenCover? var onNavigateToNote: (NoteModel) -> Void let container: MdxEditorWebviewContainer public init( url: URL, theme: Binding, editorThemeDark: Binding, editorThemeLight: Binding, editingNote: Binding, editorKeymap: Binding, container: MdxEditorWebviewContainer, onNavigateToNote: @escaping (NoteModel) -> Void, fullScreenCover: Binding? ) { self.url = url self._theme = theme self._editorThemeDark = editorThemeDark self._editorThemeLight = editorThemeLight self._editingNote = editingNote self._editorKeymap = editorKeymap self.container = container self.onNavigateToNote = onNavigateToNote if let fs = fullScreenCover { self._fullScreenCover = fs } else { self._fullScreenCover = .constant(nil) } self.onNavigateToNote = onNavigateToNote } public var body: some View { ZStack(alignment: show ? .bottomTrailing : .center) { MdxEditorWebviewInternal( url: url, theme: $theme, editorThemeDark: $editorThemeDark, editorThemeLight: $editorThemeLight, editingNote: $editingNote, editorKeymap: $editorKeymap, container: container, show: $show, onNavigateToNote: onNavigateToNote, fullScreenCover: $fullScreenCover, ) .disableAnimations() .frame( alignment: .bottom ) .scrollDisabled(true) if !show { ProgressView() .progressViewStyle(.circular) .scaleEffect(1.5) .tint(themeManager.theme.primary) } else { FloatingButtonView( buttons: [ FloatingButtonItem( id: "addTaggable", systemImage: "tag.fill", action: { withAnimation { showEditNoteTaggables.toggle() } } ), FloatingButtonItem( id: "toggleBookmarked", systemImage: editingNote.bookmarked ? "bookmark.fill" : "bookmark", action: { editingNote.bookmarked.toggle() } ) ] ) .padding() } } .fullScreenCover( isPresented: $showEditNoteTaggables, content: { EditNoteTaggablesView( editingNote: $editingNote, open: $showEditNoteTaggables ) }, ) } func onLoad() async { } } #endif [/code] Заранее благодарим вас за любые предложения. Я работаю со Swift всего пару месяцев, поэтому мне еще есть чему поучиться. Подробнее здесь: [url]https://stackoverflow.com/questions/79891050/inexplicable-white-background-below-swiftui-webview[/url]