Reframing SwiftUI Views. A collection of tools to help with layout.

Overview

FrameUp Logo

Swift Compatibility Platform Compatibility License - MIT Version GitHub last commit Twitter

Overview

A Swift Package with a collection of SwiftUI framing views and tools to help with layout.

FrameUpExample

Check out the example app to see how you can use this package in your iOS app.

Installation

  1. In XCode 12 go to File -> Swift Packages -> Add Package Dependency or in XCode 13 File -> Add Packages
  2. Paste in the repo's url: https://github.com/ryanlintott/FrameUp and select by version.

Usage

Import the package using import FrameUp

Platforms

This package is compatible with iOS 14 or later. It's technically compatible with macOS 11 but hasn't been tested yet.

Is this Production-Ready?

Really it's up to you. I currently use this package in my own Old English Wordhord app.

Support

If you like this package, buy me a coffee to say thanks!

ko-fi


Details

Size Readers

Unlike 'GeometryReader' these views will provide measurement of only one axis and will only take up as much space on the other axis as is needed for their child views.

WidthReader

Provides the available width while fitting to the height of the content.

Useful inside vertical scroll views where you want to get the width without specifying a frame height.

Example:

ScrollView {
    WidthReader { width in
        HStack(spacing: 0) {
            Text("This text frame is set to 70% of the width.")
                .frame(width: width * 0.7)
                .background(Color.green)

            Circle()
        }
    }
    .foregroundColor(.white)
    .background(Color.blue)

    Text("The WidthReader above does not have a fixed height and will grow to fit the content.")
        .padding()
}

HeightReader

Provides the available height while fitting to the width of the content.

Useful inside horizontal scroll views where you want to get the height without specifying a frame width. Example:

ScrollView(.horizontal) {
    HeightReader { height in
        VStack(spacing: 0) {
            Text("This\ntext\nframe\nis\nset\nto\n70%\nof\nthe\nheight.")
                .frame(height: height * 0.7)
                .background(Color.green)

            Circle()
        }
        .foregroundColor(.white)
        .background(Color.blue)

        Text("\nThe\nHeightReader\nto\nthe\nleft\ndoes\nnot\nhave\na\nfixed\nwidth\nand\nwill\ngrow\nto\nfit\nthe\ncontent.")
            .padding()
    }
}

.onSizeChange(perform:)

Adds an action to perform when parent view size value changes.

struct OnSizeChangeExample: View {
    @State private var size: CGSize = .zero
    
    var body: some View {
        Text("Hello, World!")
            .padding(100)
            .background(Color.blue)
            .onSizeChange { size in
                self.size = size
            }
            .overlay(Text("size: \(size.width) x \(size.height)"), alignment: .bottom)
    }
}

SmartScrollView

A ScrollView with extra features.

  • Optional Scrolling - When active, the view will only be scrollable if the content is too large to fit in the parent frame.
  • Shrink to Fit - When active, the view will only take as much vertical and horizontal space as is required to fit the content.
  • Edge Insets - An onScroll function runs when the view is scrolled and reports the edge insets. Insets are negative when content edges are beyond the scroll view edges.

These features are disabled by default and can be enabled in any combination. Similar to a standard ScrollView you can also specify the axes and toggle showsIndicators.

Example:

SmartScrollView(.vertical, showsIndicators: true, optionalScrolling: true, shrinkToFit: true) {
    // Content here
} onScroll: { edgeInsets in
    // Runs when view is scrolled
}

Flow Views

HFlow

A view that creates views based on a collection of data from left to right, adding rows when needed.

Each row height will be determined by the tallest element. The overall frame size will fit to the size of the laid out content.

A maximum width must be provided but WidthReader can be used to get the value (especially helpful when inside a ScrollView).

Example:

WidthReader { width in
    HFlow(["Hello", "World", "More Text"], maxWidth: width) { item in
        Text(item.value)
            .padding(12)
            .foregroundColor(.white)
            .background(Color.blue)
            .cornerRadius(12)
            .clipped()
    }
}

Adding or removing elements may not animate as intended as element ids are based on their index.

VFlow

A view that creates views based on a collection of data from top to bottom, addcolumns when needed.

Each column width will be determined by the widest element. The overall frame swill fit to the size of the laid out content.

A maximum height must be provided but HeightReader can be used to get the va(especially helpful when inside a ScrollView).

Example:

HeightReader { height in
    HFlow(["Hello", "World", "More Text"], maxHeight: height) { item in
        Text(item.value)
            .padding(12)
            .foregroundColor(.white)
            .background(Color.blue)
            .cornerRadius(12)
            .clipped()
    }
}

Adding or removing elements may not animate as intended as element ids are based on their index.

OverlappingImage

An image view that can overlap content on the edges of its frame.

Image can overlap either on the vertical or horizontal axis but not both.

Be sure to consider spacing and use zIndex to place the image in front or behind content.

VStack(spacing: 0) {
    Text("Overlapping Image")
        .font(.system(size: 50))

    OverlappingImage(Image(systemName: "star.square"), aspectRatio: 1.0, top: 0.1, bottom: 0.25)
        .padding(.horizontal, 50)
        .zIndex(1)

    Text("The image above will overlap content above and below.")
        .padding(20)
}

.relativePadding(edges:, lengthFactor:)

A view modifier that pads its content by the specified edge insets with a percentage of the content view size. Width is used for .leading/.trailing and height is used for .top/.bottom

Negative values can be used to overlap content.

Text("This text will have padding based on the width and height of its frame.")
    .relativePadding([.leading, .top], 0.2)

TabMenuView

Customizable tab menu bar view designed to mimic the style of the default tab menu bar on iPhone. Images or views and name provied are used to mask another provided view which is often a color.

Features:

  • Use any image or AnyView as a mask for the menu item.
  • Use any view as the 'color' including gradients.
  • onReselect function that triggers when the active tab menu item is selected.
  • onDoubleTap function that triggers when any tab is double-tapped.

Example:

let items = [
    TabMenuItem(icon: AnyView(Circle().stroke().overlay(Text("i"))), name: "Info", tab: 0),
    TabMenuItem(image: Image(systemName: "star"), name: "Favourites", tab: 1),
    TabMenuItem(image: Image(systemName: "bookmark"), name: "Categories", tab: 2),
    TabMenuItem(image: Image(systemName: "books.vertical"), name: "About", tab: 3)
]

TabMenuView(selection: $selection, items: items) { isSelected in
    Group {
        if isSelected {
            Color.accentColor
        } else {
            Color(.secondaryLabel)
        }
    }
} onReselect: {
    print("TabMenu item \(selection) reselected")
} onDoubleTap: {
    print("TabMenu item \(selection) doubletapped")
}

ScaledView

A view modifier that scales a view using scaleEffect to match a frame size.

View must have an intrinsic content size or be provided a specific frame size. Final frame size may be different depending on modes chosen.

Uses ScaleMode to limit the view so it can only grow/shrink or both.

Used in these view Extensions

  • scaledToFrame(size:,contentMode:,scaleMode:)
  • scaledToFrame(width:,height:,contentMode:,scaleMode:)
  • scaledToFit(size:,scaleMode:)
  • scaledToFit(width:,height:,scaleMode:)
  • scaledToFit(width:,scaleMode:)
  • scaledToFit(height:,scaleMode:)
  • scaledToFill(size:,scaleMode:)
  • scaledToFill(width:,height:,scaleMode:)

WidgetSize

An enum similar to WidgetFamily but returns widget frame sizes by device and doesn't require WidgetKit

Cases

  • small
  • medium
  • large
  • extraLarge

Key Functions and Properties

supportedSizesForCurrentDevice

Returns an array of supported widget sizes based on device type and iOS version.

sizeForCurrentDevice

The size of this WidgetSize on the current device.

All size information from: Apple - Human Interface Guidelines

scaleFactorForCurrentDevice

How much the widget is scaled down to fit on the Home Screen.

Home Screen width divided by design canvas width. iPhone value will always be 1.

WidgetDemoFrame

Creates widget frames sized for the current device (and scaled for iPad). Used for showing example widgets from inside the app.

Corner radius size defaults to 20 and may not be the same as the actual widget corner radius.

For iPad, widget views use a design size and are scaled to a smaller Home Screen size using ScaledView. This demo frame uses the same scaling to properly preview the widget. All sizes will work on all devices and all versions of iOS (even extraLarge on iPhone with iOS 14.0).

Example:

WidgetDemoFrame(.medium, cornerRadius: 20) { size, cornerRadius in
    Text("Demo Widget")
}

WidgetRelativeShape

A re-scaled version of ContainerRelativeShape used to fix a bug with the corner radius on iPads.

Example: This widget view will have a blue background with a 1 point inset from the edge. On iPad, the red background will show on the corners as the corner radius does not match.

Text("Example widget")
    .background(.blue)
    .clipShape(WidgetRelativeShape(.systemSmall))
    .background(
        ContainerRelativeShape()
            .fill(.red)
    )
    .padding(1)

Proportionable

A protocol that adds helpful parameters like aspectFormat, aspectRatio, minDimension, and maxDimension.

Used on types that have width and height properties like CGSize.

How to add conformance in your app:

extension CGSize: Proportionable { }

View Extensions

frame(size:,alignment:)

Alternative to frame(width:,height:,alignment:) that takes a CGSize parameter instead.

You might also like...
Auto Layout made easy with the Custom Layout.
Auto Layout made easy with the Custom Layout.

Auto Layout made easy with the Custom Layout. Getting started CocoaPods CocoaPods is a dependency manager for Cocoa projects. You can install it with

AppStoreClone - Understanding the complex layout of app store using UICompositional layout in swift
AppStoreClone - Understanding the complex layout of app store using UICompositional layout in swift

AppStoreClone Understanding the complex layout of app store using UICompositiona

A collection of operators and utilities that simplify iOS layout code.

Anchorage A lightweight collection of intuitive operators and utilities that simplify Auto Layout code. Anchorage is built directly on top of the NSLa

A Code challenge I solved leveraging a lot on Composite collection view layout written in swift

AsthmApp Mobile app designed as a support aid for people with Asthma Accounts Google and Firebase [email protected] dICerytiMPSI Facebook asthmp.ap

A Code challenge I solved leveraging a lot on Composite collection view layout...written in swift

Space44 Code Challenge Space44 Code Challenge iOS application for Space 44 hiring process, it leverages on Image download and composite collection vie

A flexible collection view with proper horizontal layout flow
A flexible collection view with proper horizontal layout flow

FlexCollection A very simple flexible collection view using SwiftUI that automat

Modern-collection-view - Modern collection view for swift

Modern collection view Sample application demonstrating the use of collection vi

⌨️ KeyboardToolbar - Add tools above your keyboard with iOS-like keyboard buttons
⌨️ KeyboardToolbar - Add tools above your keyboard with iOS-like keyboard buttons

KeyboardToolbar 👀 Overview Use KeyboardToolbar to add tools as an input accessory view to a UITextField, UITextView, or any other view conforming to

A Set of Tools To Extend UIKit (Classic iOS Framework)
A Set of Tools To Extend UIKit (Classic iOS Framework)

RVS_UIKit_Toolbox A set of basic UIKit tools, for Swift iOS app development. Overview This package offers a few extensions of standard UIKit classes,

Comments
  • FULayout and FlippingView

    FULayout and FlippingView

    Added FULayout protocol that can build layout views using either VariadicView or a built in .forEach function. Added custom layouts: HFlow, VFlow, HMasonry, VMasonry, VStackFULayout, HStackFULayout, and ZStackFULayout. Added a type-erasing AnyLayout. Added FlippingView and TwoSidedView. Changed .rotationMatchingOrientation view modifier to AutoRotatingView. Fixed some bugs in SmartScrollView and changed optionalScrolling and shrinkToFit defaults to true. Changed WidgetRelativeShape for iOS 16 so that it no longer makes adjustments on iPads as Apple had fixed the bug that required it. Added accessibility actions to TabMenuView actions for reselect and double tap. Fixed onReselect and onDoubleTap so they don't both trigger with a double tap. Changed onDoubleTap so it only works on the selected tab.

    opened by ryanlintott 0
Releases(0.4.0)
  • 0.4.0(Sep 15, 2022)

    New Features:

    • Added FULayout protocol that can build layout views using either VariadicView or a built in .forEach function.
    • Added custom layouts: HMasonry, VMasonry, VStackFULayout, HStackFULayout, and ZStackFULayout.
    • Added a type-erasing AnyLayout.
    • Added FlippingView and TwoSidedView.
    • Added accessibility actions to TabMenuView actions for reselect and double tap.

    Changes and fixes:

    • HFlow and VFlow have changed from views to FULayout and now support various alignments.
    • Removed VGridMasonry (use VMasonry FULayout instead)
    • Changed .rotationMatchingOrientation view modifier to AutoRotatingView.
    • Fixed bugs in SmartScrollView
    • Changed SmartScrollView optionalScrolling and shrinkToFit defaults to true.
    • Changed WidgetRelativeShape for iOS 16 so that it no longer makes adjustments on iPads as Apple had fixed the bug that required it.
    • Added widget sizes to align with Apple's latest size specifications.
    • Fixed TabMenuView onReselect and onDoubleTap to use NamedAction instead of just a closure so they can support accessibility actions.
    • Fixed TabMenuView onReselect so it won't trigger alongside onDoubleTap.
    • Changed TabMenuView onDoubleTap so it only works on the selected tab.
    • Removed FixWidgetPreviewAlignmentBug as it is no longer an issue in Xcode.
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(May 23, 2022)

  • 0.2.8(Jan 31, 2022)

  • 0.2.7(Jan 25, 2022)

  • 0.2.6(Jan 21, 2022)

Owner
Ryan Lintott
Designer / Developer / Consultant
Ryan Lintott
A Swift utility to make updating table views/collection views trivially easy and reliable.

ArrayDiff An efficient Swift utility to compute the difference between two arrays. Get the removedIndexes and insertedIndexes and pass them directly a

Adlai Holler 100 Jun 5, 2022
Expose layout margins and readable content width to SwiftUI's Views

SwiftUI Layout Guides This micro-library exposes UIKit's layout margins and readable content guides to SwiftUI. Usage Make a view fit the readable con

Thomas Grapperon 26 Dec 23, 2022
SwiftUI views that arrange their children in a flow layout.

SwiftUI Flow SwiftUI views that arrange their children in a flow layout. HFlow A view that arranges its children in a horizontal flow. Usage ScrollVie

Ciaran O'Brien 114 Jan 5, 2023
SwiftUI views that arrange their children in a Pinterest-like layout

SwiftUI Masonry SwiftUI views that arrange their children in a Pinterest-like layout. HMasonry A view that arranges its children in a horizontal mason

Ciaran O'Brien 88 Dec 27, 2022
A custom layout built on top of SwiftUI's Layout API that lays elements out in multiple lines. Similar to flex-wrap in CSS, CollectionViewFlowLayout.

WrapLayout A custom layout built on top of SwiftUI's Layout API that lays elements out in multiple lines. Similar to flex-wrap in CSS, CollectionViewF

Hiroshi Kimura 6 Sep 27, 2022
Flow layout / tag cloud / collection view in SwiftUI.

SwiftUIFlowLayout A Flow Layout is a container that orders its views sequentially, breaking into a new "line" according to the available width of the

Gordan Glavaš 115 Dec 28, 2022
Swift-picker-views - inline single and multi picker views for UIKit. Without tableview! Easy and simple

swift-picker-views Inline single and multiple picker views for UIKit. No tablevi

IBRAHIM YILMAZ 2 Jan 31, 2022
Fast Swift Views layouting without auto layout. No magic, pure code, full control and blazing fast. Concise syntax, intuitive, readable & chainable. [iOS/macOS/tvOS/CALayer]

Extremely Fast views layouting without auto layout. No magic, pure code, full control and blazing fast. Concise syntax, intuitive, readable & chainabl

layoutBox 2.1k Dec 22, 2022
Fast Swift Views layouting without auto layout. No magic, pure code, full control and blazing fast

Fast Swift Views layouting without auto layout. No magic, pure code, full control and blazing fast. Concise syntax, intuitive, readable & chainable. [iOS/macOS/tvOS/CALayer]

layoutBox 2.1k Jan 2, 2023
Auto Layout (and manual layout) in one line.

Auto Layout (and manual layout) in one line. Quick Look view.bb.centerX().below(view2).size(100) It’s equivalent to iOS 9 API: view.centerXAnchor.cons

Javier Zhang 74 Oct 19, 2022