Post

Replies

Boosts

Views

Activity

Page view in SwiftUI
I have an app for musicians that works with Songs and Setlists. The logical structure is as follows: A Setlist contains Songs. A Song has Sections, which include Lines (chords & lyrics). I want to view my Setlist in a "Page View," similar to a book where I can swipe through pages. In this view, the Song Sections are wrapped into columns to save screen space. I use a ColumnsLayout to calculate and render the columns, and then a SplitToPages modifier to divide these columns into pages. Problem: The TabView sometimes behaves unexpectedly when a song spans multiple pages during rendering. This results in a transition that is either not smooth or stops between songs. Is there a better way to implement this behavior? Any advice would be greatly appreciated. struct TestPageView: View { struct SongWithSections: Identifiable { var id = UUID() var title: String var section: [String] } var songSetlistSample: [SongWithSections] { var songs: [SongWithSections] = [] //songs for i in 0...3 { var sections: [String] = [] for _ in 0...20 { sections.append(randomSection() + "\n\n") } songs.append(SongWithSections(title: "Song \(i)", section: sections)) } return songs } func randomSection() -> String { var randomSection = "" for _ in 0...15 { randomSection.append(String((0..<Int.random(in: 3..<10)).map{ _ in "abcdefghijklmnopqrstuvwxyz".randomElement()! }) + " ") } return randomSection } var body: some View { GeometryReader {geo in TabView { ForEach(songSetlistSample, id:\.id) {song in let columnWidth = geo.size.width / 2 //song ColumnsLayout(columns: 2, columnWidth: columnWidth, height: geo.size.height) { Text(song.title) .font(.largeTitle) ForEach(song.section, id:\.self) {section in Text(section) } } .modifier(SplitToPages(pageWidth: geo.size.width, id: song.id)) } } .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) } } } public struct ColumnsLayout: Layout { var columns: Int let columnWidth: CGFloat let height: CGFloat let spacing: CGFloat = 10 public static var layoutProperties: LayoutProperties { var properties = LayoutProperties() properties.stackOrientation = .vertical return properties } struct Column { var elements: [(index: Int, size: CGSize, yOffset: CGFloat)] = [] var xOffset: CGFloat = .zero var height: CGFloat = .zero } public func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) -> CGSize { let columns = arrangeColumns(proposal: proposal, subviews: subviews, cache: &cache) guard let maxHeight = columns.map({ $0.height}).max() else {return CGSize.zero} let width = Double(columns.count) * self.columnWidth return CGSize(width: width, height: maxHeight) } public func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) { let columns = arrangeColumns(proposal: proposal, subviews: subviews, cache: &cache) for column in columns { for element in column.elements { let x: CGFloat = column.xOffset let y: CGFloat = element.yOffset let point = CGPoint(x: x + bounds.minX, y: y + bounds.minY) let proposal = ProposedViewSize(width: self.columnWidth, height: proposal.height ?? 100) subviews[element.index].place(at: point, anchor: .topLeading, proposal: proposal) } } } private func arrangeColumns(proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) -> [Column] { var currentColumn = Column() var columns = [Column]() var colNumber = 0 var currentY = 0.0 for index in subviews.indices { let proposal = ProposedViewSize(width: self.columnWidth, height: proposal.height ?? 100) let size = subviews[index].sizeThatFits(proposal) let spacing = size.height > 0 ? spacing : 0 if currentY + size.height > height { currentColumn.height = currentY columns.append(currentColumn) colNumber += 1 currentColumn = Column() currentColumn.xOffset = Double(colNumber) * (self.columnWidth) currentY = 0.0 } currentColumn.elements.append((index, size, currentY)) currentY += size.height + spacing } currentColumn.height = currentY columns.append(currentColumn) return columns } } struct SplitToPages: ViewModifier { let pageWidth: CGFloat let id: UUID @State private var pages = 1 func body(content: Content) -> some View { let contentWithGeometry = content .background( GeometryReader { geometryProxy in Color.clear .onChange(of: geometryProxy.size) {newSize in guard newSize.width > 0, pageWidth > 0 else {return} pages = Int(ceil(newSize.width / pageWidth)) } .onAppear { guard geometryProxy.size.width > 0, pageWidth > 0 else {return} pages = Int(ceil(geometryProxy.size.width / pageWidth)) } }) Group { ForEach(0..<pages, id:\.self) {p in ZStack(alignment: .topLeading) { contentWithGeometry .offset(x: -Double(p) * pageWidth, y: 0) .frame(width: pageWidth, alignment: .leading) VStack { Spacer() HStack { Spacer() Text("\(p + 1) of \(pages)") .padding([.leading, .trailing]) } } } .id(id.description + p.description) } } } }
0
0
205
Nov ’24
iOS 18 navigationBar color scheme not working
Hello, I'm encountering an issue with toolbarColorScheme in iOS 18. In a simple example, toolbarColorScheme works fine when triggered in onAppear. However, after navigating to a different view (e.g., following a link, such as link 234), and then returning, the toolbarColorScheme seems to be ignored. Could anyone help me resolve this issue? struct TestNavigationBarColor: View { var body: some View { NavigationStack { List { Text("123") NavigationLink(value: 1) { Text("234") } } .toolbarColorScheme(.dark, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar) .navigationTitle("Title1") .navigationDestination(for: Int.self) {_ in List { Text("Nested screen") } .navigationTitle("Title2") } } } }
1
0
467
Sep ’24
Reading LogicPro pasteboard
Hello, my app works as Auv3 plugin. I am interested in copying / pasting LogicPro chord track. After I copy chord track in LogicPro and read UIPasteBoard.general in the app, I can see: ["LogicPasteBoardMarker": <OS_dispatch_data: data[0x3024599c0] = { leaf, size = 1, buf = 0x10a758000 }>] How can I access these data? Thank you.
0
0
483
Jun ’24