Saturday, December 21, 2024
HomeiOS DevelopmentProgressive Net Apps on iOS

Progressive Net Apps on iOS

[ad_1]


The way to make a PWA for iOS?

A progressive net utility is only a particular sort of web site, that may look and behave like a local iOS app. As a way to construct a PWA, first we will create a daily web site utilizing SwiftHtml. We are able to begin with a daily executable Swift bundle with the next dependencies.



import PackageDescription

let bundle = Package deal(
    title: "Instance",
    platforms: [
        .macOS(.v12)
    ],
    dependencies: [
        .package(url: "https://github.com/binarybirds/swift-html", from: "1.2.0"),
        .package(url: "https://github.com/vapor/vapor", from: "4.54.0"),
    ],
    targets: [
        .executableTarget(name: "Example", dependencies: [
            .product(name: "SwiftHtml", package: "swift-html"),
            .product(name: "Vapor", package: "vapor"),
        ]),
        .testTarget(title: "ExampleTests", dependencies: ["Example"]),
    ]
)


As you possibly can see we will use the vapor Vapor library to serve our HTML website. If you do not know a lot about Vapor let’s simply say that it’s a net utility framework, which can be utilized to construct server aspect Swift purposes, it is a fairly superb device I’ve a newbie’s information put up about it.


After all we will want some elements for rendering views utilizing SwiftHtml, you should utilize the supply snippets from my earlier article, however right here it’s once more how the SwiftHtml-based template engine ought to appear to be. It’s best to learn my different article if you wish to know extra about it. 🤓


import Vapor
import SwiftSgml

public protocol TemplateRepresentable {
    
    @TagBuilder
    func render(_ req: Request) -> Tag
}

public struct TemplateRenderer {
    
    var req: Request
    
    init(_ req: Request) {
        self.req = req
    }

    public func renderHtml(_ template: TemplateRepresentable, minify: Bool = false, indent: Int = 4) -> Response {
        let doc = Doc(.html) { template.render(req) }
        let physique = DocumentRenderer(minify: minify, indent: indent).render(doc)
        return Response(standing: .okay, headers: ["content-type": "text/html"], physique: .init(string: physique))
    }
}

public extension Request {
    var templates: TemplateRenderer { .init(self) }
}


We’re additionally going to wish an index template for our fundamental HTML doc. Since we’re utilizing a Swift DSL to write down HTML code we do not have to fret an excessive amount of about mistyping a tag, the compiler will shield us and helps to take care of a totally legitimate HTML construction.



import Vapor
import SwiftHtml


struct IndexContext {
    let title: String
    let message: String
}

struct IndexTemplate: TemplateRepresentable {
    
    let context: IndexContext
    
    init(_ context: IndexContext) {
        self.context = context
    }
    
    func render(_ req: Request) -> Tag {
        Html {
            Head {
                Title(context.title)
                
                Meta().charset("utf-8")
                Meta().title(.viewport).content material("width=device-width, initial-scale=1")
            }
            Physique {
                Most important {
                    Div {
                        H1(context.title)
                        P(context.message)
                    }
                }
            }
        }
    }
}


Lastly we are able to merely render the bootstrap our Vapor server occasion, register our route handler and render the index template inside the principle entry level of our Swift bundle by utilizing the beforehand outlined template helper strategies on the Request object.


import Vapor
import SwiftHtml

var env = attempt Surroundings.detect()
attempt LoggingSystem.bootstrap(from: &env)
let app = Utility(env)
defer { app.shutdown() }

app.get { req -> Response in
    let template = IndexTemplate(.init(title: "Whats up, World!",
                                    message: "This web page was generated by the SwiftHtml library."))
    
    return req.templates.renderHtml(template)
}

attempt app.run()


It’s simply that simple to setup and bootstrap a totally working net server that’s able to rendering a HTML doc utilizing the ability of Swift and the Vapor framework. If you happen to run the app you must be capable of see a working web site by visiting the http://localhost:8080/ tackle.


Turning a web site into an actual iOS PWA

Now if we wish to rework our web site right into a standalone PWA, now we have to supply a hyperlink a particular net app manifest file inside the pinnacle part of the index template.



Meta tags vs manifest.json


Looks as if Apple follows sort of an odd route if it involves PWA assist. They’ve fairly a historical past of “considering exterior of the field”, this mindset applies to progressive net apps on iOS, since they do not are inclined to comply with the requirements in any respect. For Android gadgets you could possibly create a manifest.json file with some predefined keys and you would be simply positive together with your PWA. Alternatively Apple these days prefers numerous HTML meta tags as an alternative of the online manifest format.


Personally I do not like this strategy, as a result of your HTML code shall be bloated with all of the PWA associated stuff (as you may see that is going to occur if it involves launch display photos) and I imagine it is higher to separate these sort of issues, however hey it is Apple, they can not be incorrect, proper? 😅


Anyhow, let me present you easy methods to assist numerous PWA options on iOS.


Enabling standalone app mode


The very first few keys that we would like so as to add to the index template has the apple-mobile-web-app-capable title and you must use the “sure” string as content material. This can point out that the app ought to run in full-screen mode, in any other case it should be displayed utilizing Safari similar to a daily website.


struct IndexTemplate: TemplateRepresentable {
    
    let context: IndexContext
    
    init(_ context: IndexContext) {
        self.context = context
    }
    
    func render(_ req: Request) -> Tag {
        Html {
            Head {
                Title(context.title)
                
                Meta().charset("utf-8")
                Meta().title(.viewport).content material("width=device-width, initial-scale=1")

                Meta()
                    .title(.appleMobileWebAppCapable)
                    .content material("sure")
            }
            Physique {
                Most important {
                    Div {
                        H1(context.title)
                        P(context.message)
                    }
                }
            }
        }
    }
}


We should always change the hostname of the server and hear on the 0.0.0.0 tackle, this manner in case your telephone is on the identical native WiFi community you must be capable of attain your net server instantly.


import Vapor
import SwiftHtml

var env = attempt Surroundings.detect()
attempt LoggingSystem.bootstrap(from: &env)
let app = Utility(env)
defer { app.shutdown() }

app.http.server.configuration.hostname = "0.0.0.0"
if let hostname = Surroundings.get("SERVER_HOSTNAME") {
    app.http.server.configuration.hostname = hostname
}

app.get { req -> Response in
    let template = IndexTemplate(.init(title: "Whats up, World!",
                                    message: "This web page was generated by the SwiftHtml library."))
    
    return req.templates.renderHtml(template)
}

attempt app.run()


Yow will discover out your native IP tackle by typing the next command into the Terminal app.



ifconfig | grep -Eo 'inet (addr:)?([0-9]*.){3}[0-9]*' | grep -Eo '([0-9]*.){3}[0-9]*' | grep -v '127.0.0.1'

ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*.){3}[0-9]*).*/2/p'


Simply use that IP tackle and go to the http://[ip-address]:8080/ web site utilizing your iOS system, then you must be capable of add your web site to your house display as a bookmark. Simply faucet the Share icon utilizing Safari and choose the Add to House Display menu merchandise from the record. On the brand new display faucet the Add button on the highest proper nook, this may create a brand new icon on your house display as a bookmark to your web page. Optionally, you possibly can present a customized title for the bookmark. ☺️


Since we have added the meta tag, for those who contact the newly created icon it ought to open the webpage as a standalone app (with out utilizing the browser). After all the app remains to be only a web site rendered utilizing an internet view. The standing bar will not match the white background and it has no customized icon or splash display but, however we will repair these points proper now. 📱


Customized title and icon

To supply a customized title we simply have so as to add a brand new meta tag, happily the SwiftHtml library has predefined enums for all of the Apple associated meta names, so you do not have to kind that a lot. The icon state of affairs is a little more tough, since now we have so as to add a bunch of measurement variants.


struct IndexTemplate: TemplateRepresentable {
    
    let context: IndexContext
    
    init(_ context: IndexContext) {
        self.context = context
    }
    
    func render(_ req: Request) -> Tag {
        Html {
            Head {
                Title(context.title)
                
                Meta().charset("utf-8")
                Meta().title(.viewport).content material("width=device-width, initial-scale=1")
                
                Meta()
                    .title(.appleMobileWebAppCapable)
                    .content material("sure")
                
                Meta()
                    .title(.appleMobileWebAppTitle)
                    .content material("Whats up PWA")
                
                Hyperlink(rel: .appleTouchIcon)
                    .href("https://i0.wp.com/www.endzone247.com/img/apple/icons/192.png")

                for measurement in [57, 72, 76, 114, 120, 144, 152, 180] {
                    Hyperlink(rel: .appleTouchIcon)
                        .sizes("(measurement)x(measurement)")
                        .href("/img/apple/icons/(measurement).png")
                }
            }
            Physique {
                Most important {
                    Div {
                        H1(context.title)
                        P(context.message)
                    }
                }
            }
        }
    }
}


As you possibly can see icons are referenced by utilizing the Hyperlink tag, utilizing the Apple contact icon rel attribute. The default icon with out the sizes attribute could be a 192×192 pixel picture, plus I am offering some smaller sizes by utilizing a for loop right here. We additionally must serve these icon information by utilizing Vapor, that is why we will alter the configuration file and allow the FileFiddleware.


import Vapor
import SwiftHtml

var env = attempt Surroundings.detect()
attempt LoggingSystem.bootstrap(from: &env)
let app = Utility(env)
defer { app.shutdown() }

app.middleware.use(FileMiddleware(publicDirectory: app.listing.publicDirectory))

app.http.server.configuration.hostname = "0.0.0.0"
if let hostname = Surroundings.get("SERVER_HOSTNAME") {
    app.http.server.configuration.hostname = hostname
}

app.get { req -> Response in
    let template = IndexTemplate(.init(title: "Whats up, World!",
                                    message: "This web page was generated by the SwiftHtml library."))
    
    return req.templates.renderHtml(template)
}

attempt app.run()


By including the FileMiddleware to the app with the general public listing path configuration your server app is ready to serve static information from the Public listing. Be happy to create it and place the app icons underneath the Public/img/apple/icons folder. In case you are working the server from the command line you may be positive, however if you’re utilizing Xcode you must specify a customized working listing for Vapor, this may permit the system to lookup the general public information from the precise place.


Your customized icons will not present up if you’re utilizing a self-signed certificates.


Construct and run the server and attempt to bookmark your web page once more utilizing your telephone. While you see the add bookmark web page you must be capable of validate that the app now makes use of the predefined Whats up PWA title and the picture preview ought to present the customized icon file as an alternative of a screenshot of the web page.


Correct standing bar coloration for iOS PWAs

Lengthy story brief, there’s a nice article on CSS-Tips about the newest model of Safari and the way it handles numerous theme colours on completely different platforms. It is an awesome article, you must positively learn it, however in a lot of the instances you will not want this a lot information, however you merely wish to assist gentle and darkish mode to your progressive net app. That is what I’ll present you right here.


For gentle mode we will use a white background coloration and for darkish mode we use black. We’re additionally going to hyperlink a brand new type.css file so we are able to change the background of the location and the font coloration in line with the present coloration scheme. First, the brand new meta tags to assist theme colours each for gentle and darkish mode.


struct IndexTemplate: TemplateRepresentable {
    
    let context: IndexContext
    
    init(_ context: IndexContext) {
        self.context = context
    }
    
    func render(_ req: Request) -> Tag {
        Html {
            Head {
                Title(context.title)
                
                Meta().charset("utf-8")
                Meta().title(.viewport).content material("width=device-width, initial-scale=1")
                
                Meta()
                    .title(.appleMobileWebAppCapable)
                    .content material("sure")
                Meta()
                    .title(.appleMobileWebAppTitle)
                    .content material("Whats up PWA")
                
                Meta()
                    .title(.colorScheme)
                    .content material("gentle darkish")
                Meta()
                    .title(.themeColor)
                    .content material("#fff")
                    .media(.prefersColorScheme(.gentle))
                Meta()
                    .title(.themeColor)
                    .content material("#000")
                    .media(.prefersColorScheme(.darkish))
                
                Hyperlink(rel: .stylesheet)
                    .href("/css/type.css")
                
                Hyperlink(rel: .appleTouchIcon)
                    .href("https://i0.wp.com/www.endzone247.com/img/apple/icons/192.png")
                for measurement in [57, 72, 76, 114, 120, 144, 152, 180] {
                    Hyperlink(rel: .appleTouchIcon)
                        .sizes("(measurement)x(measurement)")
                        .href("/img/apple/icons/(measurement).png")
                }
            }
            Physique {
                Most important {
                    Div {
                        H1(context.title)
                        P(context.message)
                    }
                }
            }
        }
    }
}


Contained in the type CSS file we are able to use a media question to detect the popular coloration scheme, similar to we did it for the .themeColor meta tag utilizing SwiftHtml.


physique {
    background: #fff;
    coloration: #000;
}
@media (prefers-color-scheme: darkish) {
    physique {
        background: #000;
        coloration: #fff;
    }
}


That is it, now the standing bar ought to use the identical coloration as your fundamental background. Attempt to change between darkish and lightweight mode and ensure all the things works, there’s a cool PWA demo venture right here with completely different colours for every mode if you wish to double verify the code. ✅


Splash display assist

Trace: it is ridiculous. Splash screens on iOS are problematic. Even native apps are inclined to cache the incorrect splash display or will not render PNG information correctly, now if it involves PWAs this is not essential higher. I used to be capable of present splash display photos for my app, however it took me fairly some time and switching between darkish and lightweight mode is completely damaged (so far as I do know it). 😅


As a way to cowl each single system display measurement, you must add numerous linked splash photos to your markup. It is so ugly I even needed to create a bunch of extension strategies to my index template.


extension IndexTemplate {
    
    @TagBuilder
    func splashTags() -> [Tag] {
        splash(320, 568, 2, .panorama)
        splash(320, 568, 2, .portrait)
        splash(414, 896, 3, .panorama)
        splash(414, 896, 2, .panorama)
        splash(375, 812, 3, .portrait)
        splash(414, 896, 2, .portrait)
        splash(375, 812, 3, .panorama)
        splash(414, 736, 3, .portrait)
        splash(414, 736, 3, .panorama)
        splash(375, 667, 2, .panorama)
        splash(375, 667, 2, .portrait)
        splash(1024, 1366, 2, .panorama)
        splash(1024, 1366, 2, .portrait)
        splash(834, 1194, 2, .panorama)
        splash(834, 1194, 2, .portrait)
        splash(834, 1112, 2, .panorama)
        splash(414, 896, 3, .portrait)
        splash(834, 1112, 2, .portrait)
        splash(768, 1024, 2, .portrait)
        splash(768, 1024, 2, .panorama)
    }
    
    @TagBuilder
    func splash(_ width: Int,
                _ peak: Int,
                _ ratio: Int,
                _ orientation: MediaQuery.Orientation) -> Tag {
        splashTag(.gentle, width, peak, ratio, orientation)
        splashTag(.darkish, width, peak, ratio, orientation)
    }
        
    func splashTag(_ mode: MediaQuery.ColorScheme,
                   _ width: Int,
                   _ peak: Int,
                   _ ratio: Int,
                   _ orientation: MediaQuery.Orientation) -> Tag {
        Hyperlink(rel: .appleTouchStartupImage)
            .media([
                .prefersColorScheme(mode),
                .deviceWidth(px: width),
                .deviceHeight(px: height),
                .webkitDevicePixelRatio(ratio),
                .orientation(orientation),
            ])
            .href("/img/apple/splash/(calc(width, peak, ratio, orientation))(mode == .gentle ? "" : "_dark").png")
    }
    
    func calc(_ width: Int,
              _ peak: Int,
              _ ratio: Int,
              _ orientation: MediaQuery.Orientation) -> String {
        let w = String(width * ratio)
        let h = String(peak * ratio)
        change orientation {
        case .portrait:
            return w + "x" + h
        case .panorama:
            return h + "x" + w
        }
    }
}


Now I can merely add the splashTags() name into the pinnacle part, however I am unsure if the result’s one thing I can completely agree with. Right here, check out the top of this tutorial about splash screens, the code required to assist iOS splash screens could be very lengthy and I have never even instructed you in regards to the 40 completely different picture information that you will want. Persons are actually utilizing PWA asset mills to cut back the time wanted to generate these sort of photos, as a result of it is fairly uncontrolled. 💩


I am utilizing this method in Feather CMS, so if you would like to obtain some pattern information you possibly can seize them from GitHub. I am not pleased with splash display assist, however hey, it simply works, proper? 🤔


Secure space & the notch


A particular matter I would like to speak about is the protected space assist and the notch. I can extremely suggest to learn this text on CSS-Tips about The Notch and CSS first, however the principle trick is that we are able to use 4 environmental variables in CSS to set correct margin and padding values.


First now we have to vary the viewport meta tag and lengthen our web page past the protected space. This may be completed by utilizing the viewport-fit cowl worth. Contained in the physique of the template we will add a header and a footer part, these areas may have customized background colours and fill the display.


struct IndexTemplate: TemplateRepresentable {
    
    let context: IndexContext
    
    init(_ context: IndexContext) {
        self.context = context
    }
    
    func render(_ req: Request) -> Tag {
        Html {
            Head {
                Title(context.title)
                
                Meta()
                    .charset("utf-8")
                Meta()
                    .title(.viewport)
                    .content material("width=device-width, initial-scale=1, viewport-fit=cowl")
                    
                
                Meta()
                    .title(.appleMobileWebAppCapable)
                    .content material("sure")
                Meta()
                    .title(.appleMobileWebAppTitle)
                    .content material("Whats up PWA")
                
                Meta()
                    .title(.colorScheme)
                    .content material("gentle darkish")
                Meta()
                    .title(.themeColor)
                    .content material("#fff")
                    .media(.prefersColorScheme(.gentle))
                Meta()
                    .title(.themeColor)
                    .content material("#000")
                    .media(.prefersColorScheme(.darkish))
                
                Hyperlink(rel: .stylesheet)
                    .href("/css/type.css")
                
                Hyperlink(rel: .appleTouchIcon)
                    .href("https://i0.wp.com/www.endzone247.com/img/apple/icons/192.png")
                for measurement in [57, 72, 76, 114, 120, 144, 152, 180] {
                    Hyperlink(rel: .appleTouchIcon)
                        .sizes("(measurement)x(measurement)")
                        .href("/img/apple/icons/(measurement).png")
                }
                
                splashTags()
            }
            Physique {
                Header {
                    Div {
                        P("Header space")
                    }
                    .class("safe-area")
                }
                
                Most important {
                    Div {
                        Div {
                            H1(context.title)
                            for _ in 0...42 {
                                P(context.message)
                            }
                            A("Refresh web page")
                                .href("https://theswiftdev.com/")
                        }
                        .class("wrapper")
                    }
                    .class("safe-area")
                }

                Footer {
                    Div {
                        P("Footer space")
                    }
                    .class("safe-area")
                }
            }
        }
    }
}


Besides the background coloration we do not need different content material to stream exterior the protected space, so we are able to outline a brand new CSS class and place some margins on it based mostly on the surroundings. Additionally we can safely use the calc CSS perform if we wish to add some further worth to the surroundings.


* {
    margin: 0;
    padding: 0;
}
physique {
    background: #fff;
    coloration: #000;
}
header, footer {
    padding: 1rem;
}
header {
    background: #eee;
}
footer {
    background: #eee;
    padding-bottom: calc(1rem + env(safe-area-inset-bottom));
}
.safe-area {
    margin: 0 env(safe-area-inset-right) 0 env(safe-area-inset-left);
}
.wrapper {
    padding: 1rem;
}
@media (prefers-color-scheme: darkish) {
    physique {
        background: #000;
        coloration: #fff;
    }
    header {
        background: #222;
    }
    footer {
        background: #222;
    }
}


It appears to be like good, however what if we would like to make use of customized kinds for the PWA model solely?


Detecting standalone mode


If you wish to use the show mode media question in your CSS file now we have so as to add a manifest file to our PWA. Yep, that is proper, I’ve talked about earlier than that Apple prefers to make use of meta tags and hyperlinks, however if you wish to use a CSS media question to verify if the app runs in a standalone mode you may should create an internet manifest.json file with the next contents.


{
  "show": "standalone"
}


Subsequent you must present a hyperlink to the manifest file contained in the template file.


struct IndexTemplate: TemplateRepresentable {
    
    
    
    func render(_ req: Request) -> Tag {
        Html {
            Head {
                
                
                splashTags()
                
                Hyperlink(rel: .manifest)
                    .href("/manifest.json")
            }
            Physique {
                
            }
        }
    }
}


Within the CSS file now you should utilize the display-mode selector to verify if the app is working in a standalone mode, you possibly can even mix these selectors and detect standalone mode and darkish mode utilizing a single question. Media queries are fairly helpful. 😍


/* ... */

@media (display-mode: standalone) {
    header, footer {
        background: #fff;
    }
    header {
        place: sticky;
        prime: 0;
        border-bottom: 1px strong #eee;
    }
}
@media (display-mode: standalone) and (prefers-color-scheme: darkish) {
    header, footer {
        background: #000;
    }
    header {
        border-bottom: 1px strong #333;
    }
}


You may flip the header right into a sticky part by utilizing the place: sticky attribute. I often favor to comply with the iOS type when the web site is offered to the end-user as a standalone app and I preserve the unique theme colours for the online solely.


Remember to rebuild the backend server, earlier than you take a look at your app. Since we have made some meta modifications you may need to delete the PWA bookmark and set up it once more to make issues work. ⚠️


[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments