SwifWeb Libraries: Materialize CSS

Written by imike | Published 2023/03/21
Tech Story Tags: swift | webassembly | tutorial | materialize | css | programming | webdev | hackernoon-top-story | hackernoon-es | hackernoon-hi | hackernoon-zh | hackernoon-vi | hackernoon-fr | hackernoon-pt | hackernoon-ja

TLDR In this article I explain how to use the MaterializeCSS front-end framework with the SwifWeb. Materialize is a modern responsive framework based on Material Design.via the TL;DR App

Web development without ready-to-use UI libraries wouldn't be as fast and efficient. You may argue that you're an HTML/CSS ninja, and I believe you because I am too. Nonetheless, it's still cool to use predefined stylish components, and that's why we are looking for some beautiful UI libraries.

My name is Mikhail Isaev, and I'm the author of SwifWeb framework. Today, I'm going to tell you about the MaterializeCSS framework and how to use it with SwifWeb. Let's begin!

What is it?

MaterializeCSS is a modern responsive open-source front-end framework based on Material Design which was created by Google to combine the classic principles of successful design along with innovation and technology. I enjoyed writing a wrapper for this framework because it is very easy to use!

How to install?

If you are new to SwifWeb then you have to create new project using the Webber CLI tool.

In the project open Package.swift and edit the dependencies section to make it look like this:

dependencies: [
    // the rest of the other dependencies incuding swifweb/web
    .package(url: "https://github.com/swifweb/materialize", from: "1.0.0"),
]

Next edit executableTarget to make it look like this:

.executableTarget(name: "App", dependencies: [
    .product(name: "Web", package: "web"),
    .product(name: "Materialize", package: "materialize")
]),

Then open App.swift and configure Materialize in didFinishLaunching method like this:

Lifecycle.didFinishLaunching {
    Materialize.configure()
}

You will face missing styles if you forget to configure it 🙃

Custom Theme:

If you generated a custom Materialize style from SCSS then configure it this way:

Lifecycle.didFinishLaunching { app in
    Materialize.configure(avoidStyles: true)
    app.addStylesheet("/css/custom-materialize.min.css")
}

Add your custom style into /Sources/App/css/custom-materialize.min.css

open Package.swift and declare resources in executableTarget as follows:

.executableTarget(name: "App", dependencies: [
    .product(name: "Web", package: "web"),
    .product(name: "Materialize", package: "materialize")
], resources: [
    .copy("css/custom-materialize.min.css"),
    .copy("css")
]),

Now you are ready to start building the UI!

Components

I have beautifully wrapped all the components into Swift, but the article would be too long if I were to describe all of them.

Let me examine a few of them here, and you can refer to the readme on GitHub for the rest.

Buttons

You can make any element as material button just by calling .materialButton(...) method.

A element can be used for links, and Div or other elements can be used with onClick handler.

Div("My Button")
    .materialButton(type: .raised)
    .onClick {}
A("My Link")
    .href("https://google.com")
    .materialButton(type: .raised)

Both elements will look same, the only difference is that A is a link and will act like a link.

Method .materialButton have few options:

type can be raised, floating, floatingHalfWay, flat

size can be small, large

waves can be light, red, yellow, orange, purple, green, teal or custom, light by default

disabled is just a flag to mark button disabled, pass @State value here to change it on the fly

Also you can add a material icon to it simply by calling .addMaterialIcon(...)

.addMaterialIcon("announcement")
// or optionally
.addMaterialIcon("announcement".size(.tiny).side(.right))

Find more about the material icon below.

Floating Action Button

It is a fixed floating action button with multiple actions. Read more about it in the official docs.

FloatingActionButton(icon: "add", color: .pink)
    .item(icon: "public", color: .red)
    .item(icon: "adb", color: .purple)
    .item(icon: "announcement", color: .blue) {
        Toast.show("Announcement!")
    }

Optional arguments

size can be large and small, it is large by default

waves can be light, red, yellow, orange, purple, green, teal or custom, light by default

direction can be top, right, bottom, left, it is top by default

mode can be hover, click, toolbar it is hover by default

Control it programmatically:

lazy var fab = FloatingActionButton(...)
fab.open() // to show the menu/toolbar
fab.close() // to show the menu/toolbar
fab.isOpen // the check if it is open

Icons

You can add an icon anywhere by initializing the MaterialIcon object.

This object accepts icon type as the initializer argument. List of the icon types is available here.

MaterialIcon("announcement")

You can change icon type on the fly later by calling method .type(...) on it.

Optional methods

.size(...) accepts tiny, small, medium, large

.side(...) accepts left, right

All the methods can accept reactive @State value, so you can change the icon and its properties on the fly.

Colors

All colors that are listed in the official documentation are available including their modifiers.

Text Color

Span("Hello").textColor(.red)          // just red color
Span("Hello").textColor(.red.lighten3) // red color with lighten-3 modifier
Span("Hello").textColor(.red.darken4)  // red color with darken-4 modifier

Background Color

Div().materialBackground(.red) // just red color
Div().textColor(.red.lighten3) // red color with lighten-3 modifier
Div().textColor(.red.darken4)  // red color with darken-4 modifier
// or
Div().red()
Div().red(.lighten3)
Div().red(.darken4)

Grid

The main thing in grid is Column which has its width and optional offest, push, pull modifiers.

Container {
    Row {
        Column(.small(.one)) { "1" }  // <div class="col s1">1</div>
        Column(.small(.one)) { "2" }  // <div class="col s1">2</div>
        Column(.small(.one)) { "3" }  // <div class="col s1">3</div>
        Column(.small(.one)) { "4" }  // <div class="col s1">4</div>
        Column(.small(.one)) { "5" }  // <div class="col s1">5</div>
        Column(.small(.one)) { "6" }  // <div class="col s1">6</div>
        Column(.small(.one)) { "7" }  // <div class="col s1">7</div>
        Column(.small(.one)) { "8" }  // <div class="col s1">8</div>
        Column(.small(.one)) { "9" }  // <div class="col s1">9</div>
        Column(.small(.one)) { "10" } // <div class="col s1">10</div>
        Column(.small(.one)) { "11" } // <div class="col s1">11</div>
        Column(.small(.one)) { "12" } // <div class="col s1">12</div>
    }
    Divider()
    Row {
        Column(.small(.twelve)) { "screen-wide" }
        Column(.small(.six)) { "one-half" }
        Column(.small(.six)) { "one-half" }
    }
    Divider()
    Row {
        Column(.small(.twelve)) { "12-columns" }
        Column(.small(.six, offset: .six)) { "6-columns at the right" }
    }
    Divider()
    Row {
        Column(.small(.seven, push: .five)) { "7-columns pushed to the right" }
        Column(.small(.five, pull: .seven)) { "5-columns pulled to the left" }
    }
}

The examples above are 1:1 like in the official documentation, so you can just compare.

You can set more than one size class to a Column:

Column(.extraLarge(.twelve), .large(.ten), .medium(.eight), .small(.six))

Alignment

Vertical Align

VAlignWrapper {
    H5("This should be vertically aligned")
}

Text Align

H5("This should be left aligned").leftAlign()
H5("This should be right aligned").rightAlign()
H5("This should be center aligned").centerAlign()

Quick Floats

Div().floatLeft()   // Quickly float things to left
Div().floatRight()  // Quickly float things to right
Div().floatCenter() // Quickly float things to center

Hiding/Showing Content

Div().hideOnSmallOnly()     // Hidden for Mobile Only
Div().hideOnMedOnly()       // Hidden for Tablet Only
Div().hideOnMedAndDown()    // Hidden for Tablet and Below
Div().hideOnMedAndUp()      // Hidden for Tablet and Above
Div().hideOnLargeOnly()     // Hidden for Desktop Only
Div().showOnSmall()         // Show for Mobile Only
Div().showOnMedium()        // Show for Tablet Only
Div().showOnLarge()         // Show for Desktop Only
Div().showOnMediumAndUp()   // Show for Tablet and Above
Div().showOnMediumAndDown() // Show for Tablet and Below
Div().pinned()              // Pins element

Formatting

Truncation

H4("This is an extremely long title").truncate() // also with @State

Hover

This feature adds an animation for box shadow on hover.

Card().hoverable() // also with @State

Browser Defaults

You can revert element styles to the original state.

Ul().browserDefault()

Images

Responsive Images

Img().src("cool_pic.jpg").responsive() // resizes responsively to page width

Circular images

Img().src("images/yuna.jpg").circle() // square image that appears circular

Videos

Responsive Embeds

Div {
    IFrame(...)
}.videoContainer()

Responsive Videos

Video {
    Source().src("movie.mp4").type("video/mp4")
}.responsive()

Navbar

Navbar {
    A("Logo")
        .floatLeft()   // align left
        .floatRight()  // align right
        .floatCenter() // align center
}
.deepPurple(.lighten4) // background
.left()                // left aligned links
.right()               // right aligned links
.item("Sass", href: "/sass")
.item("Components", href: "/badges")
.item("JavaScript", active: true, href: "/collapsible") // as active
// dropdown menu
.dropdownItem("More", background: .white, hover: false) {
    DropdownItem("Dropdown 1", color: .red) { Toast.show("Hi") }
    DropdownItem("Dropdown 2", href: "")
}

Cards

Card("Card Title")
    .blueGrey(.darken1)
    .textColor(.white)
    .message("""I am a very simple card.
    I am good at containing small bits of information.
    I am convenient because I require little markup to use effectively.
    """)
    .action("THIS IS A BUTTON") {}
    .action("THIS IS A LINK", href: "#")

Preloader

Circular

PreloaderCircular(colors: .blue, .red, .green, .yellow).active()

Indeterminate

PreloaderLinearIndeterminate()

Determinate

@State var value: Double = 20 // change it to update the progress
PreloaderLinearDeterminate($value)

Pagination

Pagination()
    .back(disabled: true, icon: "chevron_left") {}
    .item(1, href: "#1")
    .item(2, href: "#2")
    .item(3) { Toast.show("Going to 3!") }
    .forward() { Toast.show("Going forward!", rounded: true) }

That’s It

All right, I described about ~20% of the framework.

If you are familiar with Materialize CSS you may notice that its Swift representation is much easier to use!

Wanna learn more? Stay tuned for the upcoming articles!

Don’t hesitate to ask any questions in our Discord and feel free to contribute!


Written by imike | Swift Evangelist
Published by HackerNoon on 2023/03/21