From dfb22fd2a012c80fc616eb771d6417d08810d3f7 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 15 Nov 2024 17:16:15 +0100 Subject: [PATCH 1/5] fix overflowing UI --- Nos/Views/Components/Media/GalleryView.swift | 49 +++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/Nos/Views/Components/Media/GalleryView.swift b/Nos/Views/Components/Media/GalleryView.swift index 83e50e4b0..ed9d15d18 100644 --- a/Nos/Views/Components/Media/GalleryView.swift +++ b/Nos/Views/Components/Media/GalleryView.swift @@ -130,17 +130,23 @@ fileprivate struct GalleryIndexView: View { private let secondaryFill = Color.galleryIndexDotSecondary /// The scale of the circles representing tabs that aren't selected, relative to `circleSize`. - private let smallScale: CGFloat = 0.75 + private let smallScale: CGFloat = 0.5 + + /// The maximum distance (in pages) from the selected index for visible circles. + /// Circles outside this range are not displayed. + private let maxDistance = 6 var body: some View { HStack(spacing: circleSpacing) { ForEach(0.. Bool { + ((currentIndex - maxDistance)...(currentIndex + maxDistance)).contains(index) + } + + /// Calculates the scale factor for a circle at a given index. + /// + /// - Parameter index: The index of the page to evaluate. + /// - Returns: A scale factor based on the distance from `currentIndex`. + private func scaleFor(_ index: Int) -> CGFloat { + // Show all circles at full size if there are 6 or fewer pages + if numberOfPages <= 6 { + return 1.0 + } + + // Calculate the distance from the selected page + let distanceFromCenter = abs(index - currentIndex) + + // Scale circles based on distance, shrinking to `smallScale` at max distance + let scaleRange = 1.0 - smallScale + let scaleFactor = 1.0 - (CGFloat(distanceFromCenter) / CGFloat(maxDistance)) * scaleRange + + // Prevent scale from dropping below `smallScale` + return max(smallScale, scaleFactor) + } } #Preview("Multiple URLs, landscape image first") { From a9bbc0804cccaecdc05723204dbf1ba0b06826d2 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 15 Nov 2024 17:16:24 +0100 Subject: [PATCH 2/5] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a92ca06f9..4ffa00ffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Release Notes - Fix typo in minimum age warning - Fix crash when tapping Post button on macOS. [#1687](https://github.com/planetary-social/nos/issues/1687) +- Fix galleries expand past the width of the screen when the links are many. [#24](https://github.com/verse-pbc/issues/issues/24) ### Internal Changes From 5a48c67d59789fc752dae31a5bb1252d3b6714d0 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Mon, 25 Nov 2024 18:09:56 -0500 Subject: [PATCH 3/5] always show 7 dots if there are more than 7 pages; adjust dot sizes --- Nos/Views/Components/Media/GalleryView.swift | 64 +++++++++++++++----- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/Nos/Views/Components/Media/GalleryView.swift b/Nos/Views/Components/Media/GalleryView.swift index ed9d15d18..fee9020cc 100644 --- a/Nos/Views/Components/Media/GalleryView.swift +++ b/Nos/Views/Components/Media/GalleryView.swift @@ -132,9 +132,8 @@ fileprivate struct GalleryIndexView: View { /// The scale of the circles representing tabs that aren't selected, relative to `circleSize`. private let smallScale: CGFloat = 0.5 - /// The maximum distance (in pages) from the selected index for visible circles. - /// Circles outside this range are not displayed. - private let maxDistance = 6 + /// The maximum number of circles to display. + private let maxNumberOfCircles = 7 var body: some View { HStack(spacing: circleSpacing) { @@ -160,7 +159,34 @@ fileprivate struct GalleryIndexView: View { /// - Parameter index: The index of the page to evaluate. /// - Returns: `true` if the index is within `maxDistance` of the `currentIndex`; otherwise, `false`. private func shouldShowIndex(_ index: Int) -> Bool { - ((currentIndex - maxDistance)...(currentIndex + maxDistance)).contains(index) + if numberOfPages <= maxNumberOfCircles { + return true + } + + let expectedRange = currentIndex - maxNumberOfCircles / 2 ... currentIndex + maxNumberOfCircles / 2 + let realRange: ClosedRange + if expectedRange.lowerBound < 0 { + realRange = 0...expectedRange.upperBound - expectedRange.lowerBound + } else if expectedRange.upperBound >= numberOfPages { + realRange = expectedRange.lowerBound - (expectedRange.upperBound - numberOfPages + 1)...numberOfPages - 1 + } else { + realRange = expectedRange + } + + return realRange.contains(index) + } + + private func displayRange() -> ClosedRange { + let expectedRange = currentIndex - maxNumberOfCircles / 2 ... currentIndex + maxNumberOfCircles / 2 + let realRange: ClosedRange + if expectedRange.lowerBound < 0 { + realRange = 0...expectedRange.upperBound - expectedRange.lowerBound + } else if expectedRange.upperBound >= numberOfPages { + realRange = expectedRange.lowerBound - (expectedRange.upperBound - numberOfPages + 1)...numberOfPages - 1 + } else { + realRange = expectedRange + } + return realRange } /// Calculates the scale factor for a circle at a given index. @@ -169,19 +195,29 @@ fileprivate struct GalleryIndexView: View { /// - Returns: A scale factor based on the distance from `currentIndex`. private func scaleFor(_ index: Int) -> CGFloat { // Show all circles at full size if there are 6 or fewer pages - if numberOfPages <= 6 { + if numberOfPages <= maxNumberOfCircles { return 1.0 } - // Calculate the distance from the selected page - let distanceFromCenter = abs(index - currentIndex) - - // Scale circles based on distance, shrinking to `smallScale` at max distance - let scaleRange = 1.0 - smallScale - let scaleFactor = 1.0 - (CGFloat(distanceFromCenter) / CGFloat(maxDistance)) * scaleRange - - // Prevent scale from dropping below `smallScale` - return max(smallScale, scaleFactor) + let displayRange = displayRange() + if index == currentIndex { + return 1.0 + } + if displayRange.lowerBound > 0 { + if index == displayRange.lowerBound { + return 0.5 + } else if index == displayRange.lowerBound + 1 { + return 0.75 + } + } + if displayRange.upperBound < numberOfPages - 1 { + if index == displayRange.upperBound { + return 0.5 + } else if index == displayRange.upperBound - 1 { + return 0.75 + } + } + return 1.0 } } From ed724d6e0e01d39010770de2e878a5407f794b07 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 27 Nov 2024 12:16:40 -0500 Subject: [PATCH 4/5] delete duplicate files --- Nos/Views/Media/AspectRatioContainer.swift | 40 ---- Nos/Views/Media/GalleryView.swift | 214 --------------------- Nos/Views/Media/ImageButton.swift | 92 --------- Nos/Views/Media/ImageViewer.swift | 78 -------- Nos/Views/Media/LinkPreview.swift | 141 -------------- Nos/Views/Media/LinkView.swift | 27 --- Nos/Views/Media/SquareImage.swift | 24 --- Nos/Views/Media/ZoomableContainer.swift | 123 ------------ 8 files changed, 739 deletions(-) delete mode 100644 Nos/Views/Media/AspectRatioContainer.swift delete mode 100644 Nos/Views/Media/GalleryView.swift delete mode 100644 Nos/Views/Media/ImageButton.swift delete mode 100644 Nos/Views/Media/ImageViewer.swift delete mode 100644 Nos/Views/Media/LinkPreview.swift delete mode 100644 Nos/Views/Media/LinkView.swift delete mode 100644 Nos/Views/Media/SquareImage.swift delete mode 100644 Nos/Views/Media/ZoomableContainer.swift diff --git a/Nos/Views/Media/AspectRatioContainer.swift b/Nos/Views/Media/AspectRatioContainer.swift deleted file mode 100644 index 0d5d3cca0..000000000 --- a/Nos/Views/Media/AspectRatioContainer.swift +++ /dev/null @@ -1,40 +0,0 @@ -import SwiftUI - -/// A container that holds a view and crops it to fit the aspect ratio of determined by the ``orientation``. -/// When the ``orientation`` is `.portrait`, the aspect ratio of the container will be 3:4. Otherwise, it'll be 4:3. -struct AspectRatioContainer: View { - /// The orientation, which determines the aspect ratio of this container. - let orientation: MediaOrientation - - /// The content to be displayed in the container. - let content: () -> Content - - var body: some View { - Color.clear - .aspectRatio(orientation.aspectRatio, contentMode: .fit) - .overlay { - content() - } - .clipShape(.rect) - .contentShape(.rect) - } -} - -/// The orientation of the media: either landscape or portrait. -enum MediaOrientation { - /// The media is wider than it is tall. - case landscape - /// The media is taller than it is wide. - case portrait - - /// The aspect ratio to use for the view that displays this media. - /// For `landscape`, the aspect ratio will be 4:3. For `portrait`, the aspect ratio will be 3:4. - var aspectRatio: CGFloat { - switch self { - case .landscape: - 4 / 3 - case .portrait: - 3 / 4 - } - } -} diff --git a/Nos/Views/Media/GalleryView.swift b/Nos/Views/Media/GalleryView.swift deleted file mode 100644 index f502560c3..000000000 --- a/Nos/Views/Media/GalleryView.swift +++ /dev/null @@ -1,214 +0,0 @@ -import Dependencies -import SwiftUI - -/// Displays an array of URLs in a tab view with custom paging indicators. -/// If only one URL is provided, displays a ``LinkView`` with the URL. -struct GalleryView: View { - /// The URLs of the content to display. - let urls: [URL] - - /// The currently-selected tab in the tab view. - @State private var selectedTab = 0 - - /// The orientation of all media in this gallery view. Initially set to `.landscape` until we load the first URL and - /// determine its orientation, then updated to match the first item's orientation. - @State private var orientation: MediaOrientation? - - /// This essential first image determines the orientation of the gallery view. Whatever orientation this is, so the - /// rest shall be. - /// Oh, but also: it's not always an image, so this won't work if it's a video or web link. Oopsie. - @State private var firstImage: Image? - - /// The media service that loads content from URLs and determines the orientation for this gallery. - @Dependency(\.mediaService) private var mediaService - - var body: some View { - if let orientation { - if urls.count == 1, let url = urls.first { - linkView(url: url, orientation: orientation) - } else { - galleryView(orientation: orientation) - } - } else { - loadingView() - } - } - - /// Produces a tab view with custom paging indicators in the given orientation. - /// - Parameter orientation: The orientation to use for the gallery. - /// - Returns: A gallery view in the given orientation. - private func galleryView(orientation: MediaOrientation) -> some View { - VStack { - TabView(selection: $selectedTab) { - ForEach(urls.indices, id: \.self) { index in - AspectRatioContainer(orientation: orientation) { - LinkView(url: urls[index]) - .tag(index) - } - } - } - .tabViewStyle(.page(indexDisplayMode: .never)) - .frame( - minWidth: 0, - maxWidth: .infinity, - minHeight: 0, - maxHeight: .infinity - ) - .aspectRatio(orientation == .portrait ? 3 / 4 : 4 / 3, contentMode: .fit) - .padding(.bottom, 10) - .clipShape(.rect) - - GalleryIndexView(numberOfPages: urls.count, currentIndex: selectedTab) - } - .padding(.bottom, 10) - } - - /// Produces a ``LinkView`` with the given URL in the given orientation. - /// - Parameters: - /// - url: The URL to display in the ``LinkView``. - /// - orientation: The orientation to use for the ``LinkView``. - /// - Returns: A ``LinkView`` with the given URL in the given orientation. - private func linkView(url: URL, orientation: MediaOrientation) -> some View { - AspectRatioContainer(orientation: orientation) { - LinkView(url: url) - } - } - - /// A loading view that fills the space for the given `loadingOrientation` and loads the first URL to determine the - /// orientation for the gallery. - /// - Parameter loadingOrientation: The ``MediaOrientation`` to use to display the loading view. - /// Defaults to `.landscape`. - /// - Returns: A loading view in the given `loadingOrientation`. - private func loadingView(_ loadingOrientation: MediaOrientation = .landscape) -> some View { - AspectRatioContainer(orientation: loadingOrientation) { - ProgressView() - } - .task { - guard let url = urls.first else { - orientation = .landscape - return - } - - orientation = await mediaService.orientation(for: url) - } - } -} - -// Thanks for the [example](https://betterprogramming.pub/custom-paging-ui-in-swiftui-13f1347cf529) Alexandru Turcanu! - -/// Custom paging indicators for a `GalleryView`. -fileprivate struct GalleryIndexView: View { - /// The number of pages in the tab view. - let numberOfPages: Int - - /// The currently-selected tab in the tab view. - let currentIndex: Int - - /// The size of the circle representing the currently-selected tab. - private let circleSize: CGFloat = 8.0 - - /// The space between circles. - private let circleSpacing: CGFloat = 6.0 - - /// The fill style of the circle indicating which tab is selected. - private let primaryFill = LinearGradient.horizontalAccent - - /// The fill style of the circles indicating tabs that are not selected. - private let secondaryFill = Color.galleryIndexDotSecondary - - /// The scale of the circles representing tabs that aren't selected, relative to `circleSize`. - private let smallScale: CGFloat = 0.75 - - var body: some View { - HStack(spacing: circleSpacing) { - ForEach(0.. LPLinkView { - let linkView = LPLinkView(url: url) - linkView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - linkView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) - linkView.sizeToFit() - return linkView - } - - func updateUIView(_ uiView: LPLinkView, context: Context) { - let provider = LPMetadataProvider() - provider.startFetchingMetadata(for: url) { metadata, error in - guard let metadata = metadata, error == nil else { return } - DispatchQueue.main.async { - uiView.metadata = metadata - } - } - } -} - -struct LinkPreviewCarousel: View { - - var links: [URL] - - var body: some View { - if links.count == 1, let url = links.first { - - if url.isImage { - HeroImageButton(url: url) - } else { - LinkPreview(url: url) - .padding(.horizontal, 15) - .padding(.vertical, 0) - .padding(.bottom, 15) - } - } else { - TabView { - ForEach(links, id: \.self.absoluteURL) { url in - LinkPreview(url: url) - .padding(.horizontal, 15) - .padding(.vertical, 0) - } - } - .tabViewStyle(.page) - .frame(height: 320) - .padding(.bottom, 15) - } - } -} - -struct LinkPreview_Previews: PreviewProvider { - static var previews: some View { - Group { - // swiftlint:disable line_length - LinkPreviewCarousel(links: [ - URL(string: "https://image.nostr.build/77713e6c2ef5186d516a6f16fb197fca53a20677c6756a9de1afc2d5e6d96548.jpg")!, - URL(string: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.r1ZOH5E3M6WiK6aw5GRdlAHaEK%26pid%3DApi&f=1&ipt=42ae9de7730da3bda152c5980cd64b14ccef37d8f55b8791e41b4667fc38ddf1&ipo=images")!, - URL(string: "https://youtu.be/5qvdbyRH9wA?si=y_KTgLR22nH0-cs8")!, - URL(string: "https://image.nostr.build/d5e38e7d864a344872d922d7f78daf67b0d304932fcb7fe22d35263c2fcf11c2.jpg")!, - ]) - - LinkPreviewCarousel(links: [ - URL(string: "https://image.nostr.build/d5e38e7d864a344872d922d7f78daf67b0d304932fcb7fe22d35263c2fcf11c2.jpg")! - ]) - // swiftlint:enable line_length - } - } -} diff --git a/Nos/Views/Media/LinkView.swift b/Nos/Views/Media/LinkView.swift deleted file mode 100644 index 8d685f753..000000000 --- a/Nos/Views/Media/LinkView.swift +++ /dev/null @@ -1,27 +0,0 @@ -import SwiftUI - -/// Displays a preview of content from a URL or an image, depending on the URL. -struct LinkView: View { - /// The URL of the content to display. - let url: URL - - var body: some View { - if url.isImage { - ImageButton(url: url) - } else { - LPLinkViewRepresentable(url: url) - } - } -} - -#Preview("Video") { - LinkView(url: URL(string: "https://youtu.be/5qvdbyRH9wA?si=y_KTgLR22nH0-cs8")!) -} - -#Preview("Image") { - LinkView( - url: URL( - string: "https://image.nostr.build/d5e38e7d864a344872d922d7f78daf67b0d304932fcb7fe22d35263c2fcf11c2.jpg" - )! - ) -} diff --git a/Nos/Views/Media/SquareImage.swift b/Nos/Views/Media/SquareImage.swift deleted file mode 100644 index dedefc72c..000000000 --- a/Nos/Views/Media/SquareImage.swift +++ /dev/null @@ -1,24 +0,0 @@ -import SwiftUI -import SDWebImageSwiftUI - -struct SquareImage: View { - var url: URL - - var onTap: (() -> Void)? - - var body: some View { - Color.clear - .aspectRatio(1, contentMode: .fit) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .overlay { - WebImage(url: url) - .resizable() - .indicator(.activity) - .aspectRatio(contentMode: .fill) - .onTapGesture { - onTap?() - } - } - .clipShape(Rectangle()) - } -} diff --git a/Nos/Views/Media/ZoomableContainer.swift b/Nos/Views/Media/ZoomableContainer.swift deleted file mode 100644 index b0aedae59..000000000 --- a/Nos/Views/Media/ZoomableContainer.swift +++ /dev/null @@ -1,123 +0,0 @@ -import SwiftUI - -/// A container that allows its content to be zoomed. -/// - Note: Thanks, [Ido](https://stackoverflow.com/users/8157190/ido) for your -/// [answer](https://stackoverflow.com/a/76649224) on StackOverflow! -struct ZoomableContainer: View { - let content: ContainerContent - private let maxAllowedScale = 4.0 - - @State private var currentScale: CGFloat = 1.0 - @State private var tapLocation: CGPoint = .zero - - init(@ViewBuilder content: () -> ContainerContent) { - self.content = content() - } - - func doubleTapAction(location: CGPoint) { - tapLocation = location - currentScale = currentScale == 1.0 ? maxAllowedScale : 1.0 - } - - var body: some View { - ZoomableScrollView(maxAllowedScale: maxAllowedScale, scale: $currentScale, tapLocation: $tapLocation) { - content - } - .onTapGesture(count: 2, perform: doubleTapAction) - } -} - -fileprivate struct ZoomableScrollView: UIViewRepresentable { - private var content: ScrollViewContent - private let maxAllowedScale: CGFloat - - @Binding private var currentScale: CGFloat - @Binding private var tapLocation: CGPoint - - init( - maxAllowedScale: CGFloat, - scale: Binding, - tapLocation: Binding, - @ViewBuilder content: () -> ScrollViewContent - ) { - self.maxAllowedScale = maxAllowedScale - _currentScale = scale - _tapLocation = tapLocation - self.content = content() - } - - func makeUIView(context: Context) -> UIScrollView { - // Setup the UIScrollView - let scrollView = UIScrollView() - scrollView.delegate = context.coordinator // for viewForZooming(in:) - scrollView.maximumZoomScale = maxAllowedScale - scrollView.minimumZoomScale = 1 - scrollView.bouncesZoom = true - scrollView.showsHorizontalScrollIndicator = false - scrollView.showsVerticalScrollIndicator = false - scrollView.clipsToBounds = false - - // Create a UIHostingController to hold our SwiftUI content - let hostedView = context.coordinator.hostingController.view! - hostedView.translatesAutoresizingMaskIntoConstraints = true - hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - hostedView.frame = scrollView.bounds - hostedView.backgroundColor = .clear - scrollView.addSubview(hostedView) - - return scrollView - } - - func makeCoordinator() -> Coordinator { - Coordinator(hostingController: UIHostingController(rootView: content), scale: $currentScale) - } - - func updateUIView(_ uiView: UIScrollView, context: Context) { - // Update the hosting controller's SwiftUI content - context.coordinator.hostingController.rootView = content - - if uiView.zoomScale > uiView.minimumZoomScale { // Scale out - uiView.setZoomScale(currentScale, animated: true) - } else if tapLocation != .zero { // Scale in to a specific point - uiView.zoom(to: zoomRect(for: uiView, scale: uiView.maximumZoomScale, center: tapLocation), animated: true) - // Reset the location to prevent scaling to it in case of a negative scale (manual pinch) - // Use the main thread to prevent unexpected behavior - DispatchQueue.main.async { tapLocation = .zero } - } - - assert(context.coordinator.hostingController.view.superview == uiView) - } - - // MARK: - Utils - - func zoomRect(for scrollView: UIScrollView, scale: CGFloat, center: CGPoint) -> CGRect { - let scrollViewSize = scrollView.bounds.size - - let width = scrollViewSize.width / scale - let height = scrollViewSize.height / scale - let xPosition = center.x - (width / 2.0) - let yPosition = center.y - (height / 2.0) - - return CGRect(x: xPosition, y: yPosition, width: width, height: height) - } - - // MARK: - Coordinator - - class Coordinator: NSObject, UIScrollViewDelegate { - var hostingController: UIHostingController - @Binding var currentScale: CGFloat - - init(hostingController: UIHostingController, scale: Binding) { - self.hostingController = hostingController - _currentScale = scale - } - - func viewForZooming(in scrollView: UIScrollView) -> UIView? { - hostingController.view - } - - func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { - currentScale = scale - } - } -} From 363ee020818e8a23ae618c4d38e5c55a39f931de Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 27 Nov 2024 13:07:04 -0500 Subject: [PATCH 5/5] refactor and animate! --- Nos/Views/Components/Media/GalleryView.swift | 52 +++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/Nos/Views/Components/Media/GalleryView.swift b/Nos/Views/Components/Media/GalleryView.swift index fee9020cc..076328423 100644 --- a/Nos/Views/Components/Media/GalleryView.swift +++ b/Nos/Views/Components/Media/GalleryView.swift @@ -135,16 +135,36 @@ fileprivate struct GalleryIndexView: View { /// The maximum number of circles to display. private let maxNumberOfCircles = 7 + /// The range of indices to display. If there are too many pages, we only show up to `maxNumberOfCircles`. + private var displayRange: ClosedRange { + let radius = maxNumberOfCircles / 2 + let idealStart = currentIndex - radius + let idealEnd = currentIndex + radius + + switch (idealStart, idealEnd) { + case (...0, _): + // If ideal start is negative, pin to start of range + return 0...min(maxNumberOfCircles - 1, numberOfPages - 1) + case (_, numberOfPages...): + // If ideal end is greater than the number of pages, pin to end of range + return max(0, numberOfPages - maxNumberOfCircles)...numberOfPages - 1 + default: + // Ideal case - centered around current index + return idealStart...idealEnd + } + } + var body: some View { HStack(spacing: circleSpacing) { ForEach(0.. Bool { - if numberOfPages <= maxNumberOfCircles { - return true - } - - let expectedRange = currentIndex - maxNumberOfCircles / 2 ... currentIndex + maxNumberOfCircles / 2 - let realRange: ClosedRange - if expectedRange.lowerBound < 0 { - realRange = 0...expectedRange.upperBound - expectedRange.lowerBound - } else if expectedRange.upperBound >= numberOfPages { - realRange = expectedRange.lowerBound - (expectedRange.upperBound - numberOfPages + 1)...numberOfPages - 1 - } else { - realRange = expectedRange - } - - return realRange.contains(index) - } - - private func displayRange() -> ClosedRange { - let expectedRange = currentIndex - maxNumberOfCircles / 2 ... currentIndex + maxNumberOfCircles / 2 - let realRange: ClosedRange - if expectedRange.lowerBound < 0 { - realRange = 0...expectedRange.upperBound - expectedRange.lowerBound - } else if expectedRange.upperBound >= numberOfPages { - realRange = expectedRange.lowerBound - (expectedRange.upperBound - numberOfPages + 1)...numberOfPages - 1 - } else { - realRange = expectedRange - } - return realRange + displayRange.contains(index) } /// Calculates the scale factor for a circle at a given index. @@ -199,7 +192,6 @@ fileprivate struct GalleryIndexView: View { return 1.0 } - let displayRange = displayRange() if index == currentIndex { return 1.0 }