paint-brush
Web Geliştirme için Swift Nasıl Kullanılır?ile@imike
16,604 okumalar
16,604 okumalar

Web Geliştirme için Swift Nasıl Kullanılır?

ile Mikhail Isaev33m2023/03/20
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

SwifWeb, SwiftUI kullanarak web siteleri yazma olanağı sağlayan bir çerçevedir. Tüm HTML ve CSS standartlarının yanı sıra tüm web API'lerini de kapsar. Bu yazıda size SwifWeb çerçevesini kullanarak bir web sitesi oluşturmaya nasıl başlayacağınızı göstereceğim.

People Mentioned

Mention Thumbnail
featured image - Web Geliştirme için Swift Nasıl Kullanılır?
Mikhail Isaev HackerNoon profile picture
0-item
1-item

Web geliştirme dünyası çok geniştir ve her gün ortaya çıkan yeni teknolojilerin sürekli akışında kaybolmuş gibi hissetmek kolaydır. Bu yeni teknolojilerin çoğu JavaScript veya TypeScript kullanılarak oluşturulmuştur. Ancak bu makalede, doğrudan tarayıcınızın içinde yerel Swift kullanarak web geliştirmeyi size tanıtacağım ve bunun sizi etkileyeceğinden eminim.


Bu nasıl mümkün olaiblir?

Swift'in web sayfasında yerel olarak çalışması için önce WebAssembly bayt koduna derlenmesi gerekir, ardından JavaScript bu kodu sayfaya yükleyebilir. Özel araç zincirini kullanmamız ve yardımcı dosyalar oluşturmamız gerektiğinden tüm derleme süreci biraz çetrefilli; bu nedenle yardımcı CLI araçları mevcut: Carton ve Webber.

Üçüncü taraf bir araç zinciri kullanmak uygun mudur?

SwiftWasm topluluğu, orijinal Swift araç zincirini yamalayarak Swift'in WebAssembly'de derlenmesini mümkün kılmak için muazzam miktarda çalışma yaptı. Her gün orijinal takım zincirindeki değişiklikleri otomatik olarak alarak ve testler başarısız olursa çatallarını düzelterek takım zincirini güncellerler. Hedefleri resmi alet zincirinin bir parçası olmak ve bunun yakın gelecekte gerçekleşmesini umuyorlar.

Karton mu yoksa Webber mi?

Carton , SwiftWasm topluluğu tarafından yapılmıştır ve size SwiftUI kullanarak web siteleri yazma yeteneği veren bir çerçeve olan Tokamak projeleri için kullanılabilir.


Webber, SwifWeb projeleri için tasarlandı. SwifWeb, tüm HTML ve CSS standartlarının yanı sıra tüm web API'lerini sarması bakımından farklıdır.


Her ne kadar kod tutarlılığı için SwiftUI kullanarak web uygulamaları yazmayı tercih etseniz de bunun yanlış bir yaklaşım olduğuna inanıyorum çünkü web geliştirme doğası gereği farklıdır ve SwiftUI ile aynı şekilde ele alınamaz.


Bu yüzden size HTML, CSS ve web API'lerinin tüm gücünü doğrudan Swift'den kullanma olanağı veren, otomatik tamamlama ve dokümantasyon içeren güzel sözdizimini kullanan SwifWeb'i yarattım. Ve Webber aracını oluşturdum çünkü Carton, kendisi için yaratılmadığından SwifWeb uygulamalarını doğru şekilde derleyemiyor, hata ayıklayamıyor ve dağıtamıyor.


Adım Mikhail Isaev ve SwifWeb'in yazarıyım. Bu yazıda size SwifWeb'i kullanarak bir web sitesi oluşturmaya nasıl başlayacağınızı göstereceğim.

Gerekli araçlar


Süratli

Swift'in kurulu olması gerekiyor, bunu yapmanın en kolay yolu:

  • macOS'ta Xcode'u yüklemektir
  • Linux veya Windows'ta (WSL2) Swiftlang.xyz'deki komut dosyasını kullanmaktır


Diğer durumlarda resmi web sitesindeki kurulum talimatlarına bakın

Webber CLI

Uygulamalarınızı oluşturmanıza, hata ayıklamanıza ve dağıtmanıza yardımcı olmak için Webber'ı yarattım.


MacOS'ta HomeBrew ile kurulumu kolaydır ( web sitesinden yükleyin)

 brew install swifweb/tap/webber

daha sonra en son sürüme güncellemek için çalıştırmanız yeterli

 brew upgrade webber


Ubuntu veya Windows'ta (WSL2'de Ubuntu) Webber'ı manuel olarak kopyalayın ve derleyin

 sudo apt-get install binaryen curl https://get.wasmer.io -sSfL | sh apt-get install npm cd /opt sudo git clone https://github.com/swifweb/webber cd webber sudo swift build -c release sudo ln -s /opt/webber/.build/release/Webber /usr/local/bin/webber

son sürüme güncellemek için daha sonra çalıştırın

 cd /opt/webber sudo git pull sudo swift build -c release

ana dal her zaman kararlı kod içerir, bu nedenle güncellemeleri ondan almaktan çekinmeyin

Yeni proje oluşturma

Terminali açın ve çalıştırın

 webber new

İnteraktif menüde pwa veya spa seçin ve proje adını girin.


Dizini yeni oluşturulan projeyle değiştirin ve webber serve komutunu çalıştırın.

Bu komut, projenizi WebAssembly'de derleyecek, gerekli tüm dosyaları özel bir .webber klasöründe paketleyecek ve varsayılan olarak 8888 bağlantı noktasını kullanarak projenizi tüm arayüzlerde sunmaya başlayacaktır.


webber serve için ek argümanlar

  • Uygulama türü

    Aşamalı Web Uygulaması için -t pwa

    Tek Web Uygulaması için -t spa

  • Hizmet çalışanı hedefinin adı (genellikle PWA projesinde Service olarak adlandırılır)

    -s Service

  • Uygulama hedefinin adı (Varsayılan olarak App )

    -a App

  • Konsolda daha fazla bilgi yazdırın

    -v

  • Webber sunucusunun bağlantı noktası (varsayılan 8888 )

    -p 8080

Gerçek SSL gibi test etmek için -p 443 kullanın (izin verilen kendinden imzalı SSL ayarıyla)

  • Otomatik olarak başlatılacak hedef tarayıcı adı

    --browser safari veya --browser chrome

  • Hizmet çalışanlarının hatalarını ayıklamak için izin verilen kendinden imzalı SSL ayarına sahip ek tarayıcı örneği

    --browser-self-signed

  • Gizli modda ek tarayıcı örneği

    --browser-incognito

Başvuru

Uygulama Sources/App/App.swift başlar

 import Web @main class App: WebApp { @AppBuilder override var app: Configuration { Lifecycle.didFinishLaunching { app in app.registerServiceWorker("service") } Routes { Page { IndexPage() } Page("login") { LoginPage() } Page("article/:id") { ArticlePage() } Page("**") { NotFoundPage() } } MainStyle() } }

Yaşam döngüsü

İOS benzeri bir şekilde çalışır:

didFinishLaunching yeni başlatıldığında başlatılıyor

uygulama sona erdiğinde willTerminate

pencere etkin olmadığında willResignActive

pencere aktif olduğunda didBecomeActive

didEnterBackground pencere arka plana geçtiğinde

willEnterForeground pencere ön plana geçtiğinde


Buradaki en kullanışlı yöntem didFinishLaunching çünkü uygulamayı yapılandırmak için harika bir yerdir. Gerçekten bir iOS uygulaması gibi hissettirdiğini görüyorsunuz! 😀


İşte app yararlı kolaylık yöntemleri içerir:

registerServiceWorker(“serviceName“) PWA hizmet çalışanını kaydetmek için çağrı

göreceli veya harici komut dosyası eklemek için addScript(“path/to/script.js“) çağrısı

göreceli veya harici stil eklemek için addStylesheet(“path/to/style.css“) çağrısı

addFont(“path/to/font.woff”, type:) göreli veya harici yazı tipi eklemek için çağrı, isteğe bağlı olarak türü ayarlayın

addIcon(“path/to/icon“, type:color:) simge ekleme çağrısı, isteğe bağlı olarak türü ve rengi ayarlayın


Ayrıca Autolayout , Bootstrap , Materialize vb. gibi ek kitaplıkların yapılandırılacağı yerdir.

Rotalar

Geçerli URL'ye göre uygun sayfayı göstermek için yönlendirme gereklidir.


Yönlendirmenin nasıl kullanılacağını anlamak için URL'nin ne olduğunu anlamalısınız.

https://website.com/hello/world - işte /hello/world yol

Gördüğünüz gibi başlangıçta App sınıfında tüm üst düzey rotaları bildirmemiz gerekiyor.

Üst düzey, bu rotalarda bildirilen sayfaların penceredeki tüm alanı kaplayacağı anlamına gelir.


Tamam, örneğin kök rota üç şekilde ayarlanabilir

 Page("/") { IndexPage() } Page("") { IndexPage() } Page { IndexPage() }

Bence en güzeli sonuncusu 🙂


Giriş veya kayıt rotaları bu şekilde ayarlanabilir

 Page("login") { LoginPage() } Page("registration") { RegistrationPage() }


Parametreyle ilgili rotalar

 Page("article/:id") { ArticlePage() }

Yukarıdaki örnekte :id rotanın dinamik bir parçasıdır. Kendisiyle ilişkili makaleyi görüntülemek için bu tanımlayıcıyı ArticlePage sınıfından alabiliriz.

 class ArticlePage: PageController { override func didLoad(with req: PageRequest) { if let articleId = req.parameters.get("id") { // Retrieve article here } } }

Yolda birden fazla parametreniz olabilir. Hepsini aynı şekilde geri alın.

Sorguları

Yoldaki bir sonraki ilginç şey, kullanımı da çok kolay olan sorgudur . Örneğin, arama text ve age sorgusu parametrelerine sahip olmasını bekleyen /search rotasını ele alalım.

https://website.com/search**?text=Alex&age=19** - son kısım sorgudur


Arama rotasını bildirmeniz yeterli

 Page("search") { SearchPage() }

Ve SearchPage sınıfındaki sorgu verilerini şu şekilde alın

 class SearchPage: PageController { struct Query: Decodable { let text: String? let age: Int? } override func didLoad(with req: PageRequest) { do { let query = try req.query.decode(Query.self) // use optional query.text and query.age // to query search results } catch { print("Can't decode query: \(error)") } } }

Herhangi bir şey

Bunun gibi belirli yol kısmındaki herhangi bir şeyi kabul eden rotayı bildirmek için * komutunu da kullanabilirsiniz.

 Page("foo", "*", "bar") { SearchPage() }

Yukarıdaki rota foo ve bar arasındaki her şeyi kabul edecektir, örneğin /foo/aaa/bar, /foo/bbb/bar, vb.

Hepsini yakala

** işaretiyle, belirli bir yoldaki diğer rotalarla eşleşmeyen her şeyi ele alacak özel bir tümünü kapsayan rota ayarlayabilirsiniz.


Global 404 rotası oluşturmak için kullanın

 Page("**") { NotFoundPage() }

veya belirli bir yol için, örneğin kullanıcı bulunamadığında

 Page("user", "**") { UserNotFoundPage() }


Yukarıda belirtilen rotalarla durumları netleştirelim

/user/1 - eğer /user/:id için bir rota varsa o zaman UserPage değerini döndürür. Aksi halde düşecek…


Kullanıcı BulunamadıSayfa

/user/1/hello - eğer /user/:id/hello için bir rota varsa o zaman UserNotFoundPage'e düşecektir

/something - /something için bir rota yoksa NotFoundPage'e düşecektir

İç içe yönlendirme

Bir sonraki rota için sayfadaki içeriğin tamamını değil, yalnızca belirli blokları değiştirmek isteyebiliriz. FragmentRouter'ın kullanışlı olduğu yer burasıdır!


/user sayfasında sekmelerimizin olduğunu düşünelim. Her sekme bir alt rotadır ve alt rotadaki değişikliklere FragmentRouter kullanarak tepki vermek istiyoruz.


App sınıfında üst düzey rotayı bildirin

 Page("user") { UserPage() }

Ve FragmentRouter'ı UserPage sınıfında ilan edin

 class UserPage: PageController { @DOM override var body: DOM.Content { // NavBar is from Materialize library :) Navbar() .item("Profile") { self.changePath(to: "/user/profile") } .item("Friends") { self.changePath(to: "/user/friends") } FragmentRouter(self) .routes { Page("profile") { UserProfilePage() } Page("friends") { UserFriendsPage() } } } }


Yukarıdaki örnekte, FragmentRouter /user/profile ve /user/friends alt rotalarını yönetir ve bunu Navbar altında işler, böylece sayfa hiçbir zaman içeriğin tamamını yeniden yüklemez, yalnızca belirli parçaları yeniden yükler.


Aynı veya farklı alt rotalara sahip birden fazla parça da bildirilebilir ve hepsi birlikte sihir gibi çalışacaktır!


Btw FragmentRouter bir Div'dir ve onu arayarak yapılandırabilirsiniz.

 FragmentRouter(self) .configure { div in // do anything you want with the div }

Stil sayfaları

Geleneksel CSS dosyalarını kullanabilirsiniz, ancak aynı zamanda Swift'de yazılmış bir stil sayfasını kullanma gibi yeni, sihirli bir yeteneğe de sahipsiniz!

Temel bilgiler

Swift kullanarak bir CSS kuralı bildirmek için Rule nesnesine sahibiz.


Yöntemlerini çağırarak bildirimsel olarak oluşturulabilir.

 Rule(...selector...) .alignContent(.baseline) .color(.red) // or rgba/hex color .margin(v: 0, h: .auto)

veya @resultBuilder kullanarak SwiftUI benzeri bir yol

 Rule(...selector...) { AlignContent(.baseline) Color(.red) Margin(v: 0, h: .auto) }


Her iki yol da eşittir ancak ben yazdıktan hemen sonra otomatik tamamlama nedeniyle ilkini tercih ediyorum . 😀

MDN'de açıklanan tüm CSS yöntemleri mevcuttur.

Dahası, tarayıcı öneklerini otomatik olarak yönetir!

Ancak bazı özel durumlarda özel özelliği bu şekilde ayarlayabilirsiniz.

 Rule(...selector...) .custom("customKey", "customValue")

Seçici

Kuralın hangi unsurları etkileyeceğini belirlemek için bir seçici ayarlamamız gerekir. Seçiciyi veritabanındaki sorgu olarak görüyorum, ancak bu seçici sorgusunun bazı kısımlarını işaretçi olarak adlandırıyorum.


Bir işaretçi oluşturmanın en kolay yolu, onu ham dizeyi kullanarak başlatmaktır.

 Pointer("a")


Ancak doğru hızlı yol, gerekli HTML etiketinde .pointer bu şekilde çağırarak bunu oluşturmaktır.

 H1.pointer // h1 A.pointer // a Pointer.any // * Class("myClass").pointer // .myClass Id("myId").pointer // #myId

Temel işaretçilerle ilgilidir, ancak aynı zamanda :hover :first :first-child vb. gibi değiştiricileri de vardır.

 H1.pointer.first // h1:first H1.pointer.firstChild // h1:first-child H1.pointer.hover // h1:hover

Mevcut herhangi bir değiştiriciyi bildirebilirsiniz, hepsi mevcuttur.

Eğer bir şey eksikse onu eklemek için uzantı yapmaktan çekinmeyin!

Herkese eklemek için github'a çekme isteği göndermeyi unutmayın.

Ayrıca işaretçileri birleştirebilirsiniz

 H1.class(.myClass) // h1.myClass H1.id(.myId) // h1#myId H1.id(.myId).disabled // h1#myId:disabled Div.pointer.inside(P.pointer) // div p Div.pointer.parent(P.pointer) // div > p Div.pointer.immediatedlyAfter(P.pointer) // Div + p P.pointer.precededBy(Ul.pointer) // p ~ ul


Kuraldaki seçici nasıl kullanılır?

 Rule(Pointer("a")) // or Rule(A.pointer)

Kuralda birden fazla seçici nasıl kullanılır?

 Rule(A.pointer, H1.id(.myId), Div.pointer.parent(P.pointer))

Aşağıdaki CSS kodunu üretir

 a, h1#myId, div > p { }

Reaktivite

Uygulamamız için koyu ve açık stilleri tanımlayalım, daha sonra bunlar arasında kolayca geçiş yapabileceğiz.

 import Web @main class App: WebApp { enum Theme { case light, dark } @State var theme: Theme = .light @AppBuilder override var app: Configuration { // ... Lifecycle, Routes ... LightStyle().disabled($theme.map { $0 != .happy }) DarkStyle().disabled($theme.map { $0 != .sad }) } }


LightStyle ve DarkStyle ayrı dosyalarda veya örneğin App.swift'te bildirilebilir.


 class LightStyle: Stylesheet { @Rules override var rules: Rules.Content { Rule(Body.pointer).backgroundColor(.white) Rule(H1.pointer).color(.black) } } class DarkStyle: Stylesheet { @Rules override var rules: Rules.Content { Rule(Body.pointer).backgroundColor(.black) Rule(H1.pointer).color(.white) } }


Ve sonra bir sayfanın kullanıcı arayüzünde bir yerde arayın

 App.current.theme = .light // to switch to light theme // or App.current.theme = .dark // to switch to dark theme

Ve ilgili stil sayfalarını etkinleştirecek veya devre dışı bırakacaktır! Çok hoş değil mi? 😎


Ancak stilleri CSS yerine Swift'de tanımlamanın daha zor olduğunu söyleyebilirsiniz, o zaman ne anlamı var?


Ana nokta tepkiselliktir! @State'i CSS özellikleriyle kullanabilir ve değerleri anında değiştirebiliriz!


Sadece bir göz atın, bazı reaktif özelliklere sahip bir sınıf oluşturabilir ve onu çalışma zamanında istediğimiz zaman değiştirebiliriz, böylece ekrandaki bu sınıfı kullanan herhangi bir öğe güncellenecektir! Birçok öğe için sınıf değiştirmekten çok daha etkilidir!


 import Web @main class App: WebApp { @State var reactiveColor = Color.cyan @AppBuilder override var app: Configuration { // ... Lifecycle, Routes ... MainStyle() } } extension Class { static var somethingCool: Class { "somethingCool" } } class MainStyle: Stylesheet { @Rules override var rules: Rules.Content { // for all elements with `somethingCool` class Rule(Class.hello.pointer) .color(App.current.$reactiveColor) // for H1 and H2 elements with `somethingCool` class Rule(H1.class(.hello), H2.class(.hello)) .color(App.current.$reactiveColor) } }


Daha sonra kodun herhangi bir yerinden aramanız yeterli

 App.current.reactiveColor = .yellow // or any color you want

ve Stil Sayfasındaki ve onu kullanan tüm öğelerdeki rengi güncelleyecektir 😜


Ayrıca bir Stil Sayfasına ham CSS eklemek de mümkündür

 class MainStyle: Stylesheet { @Rules override var rules: Rules.Content { // for all elements with `somethingCool` class Rule(Class.hello.pointer) .color(App.current.$reactiveColor) // for H1 and H2 elements with `somethingCool` class Rule(H1.class(.hello), H2.class(.hello)) .color(App.current.$reactiveColor) """ /* Raw CSS goes here */ body { margin: 0; padding: 0; } """ } }

ham CSS dizelerini gerektiği kadar karıştırabilirsiniz

Sayfalar

Yönlendirici her rotadaki sayfaları oluşturuyor. Page, PageController'dan miras alınan herhangi bir sınıftır.


PageController'da willLoad didLoad willUnload didUnload , kullanıcı arayüzü yöntemleri buildUI ve body gibi yaşam döngüsü yöntemleri ve HTML öğeleri için özellik sarmalayıcı değişkeni bulunur.

Teknik olarak PageController yalnızca bir Div'dir ve özelliklerini buildUI yönteminde ayarlayabilirsiniz.


 class IndexPage: PageController { // MARK: - Lifecycle override func willLoad(with req: PageRequest) { super.willLoad(with: req) } override func didLoad(with req: PageRequest) { super.didLoad(with: req) // set page title and metaDescription self.title = "My Index Page" self.metaDescription = "..." // also parse query and hash here } override func willUnload() { super.willUnload() } override func didUnload() { super.didUnload() } // MARK: - UI override func buildUI() { super.buildUI() // access any properties of the page's div here // eg self.backgroundcolor(.lightGrey) // optionally call body method here to add child HTML elements body { P("Hello world") } // or alternatively self.appendChild(P("Hello world")) } // the best place to declare any child HTML elements @DOM override var body: DOM.Content { H1("Hello world") P("Text under title") Button("Click me") { self.alert("Click!") print("button clicked") } } }


Sayfanız küçükse bu kadar kısa yoldan bile ilan edebilirsiniz.

 PageController { page in H1("Hello world") P("Text under title") Button("Click me") { page.alert("Click!") print("button clicked") } } .backgroundcolor(.lightGrey) .onWillLoad { page in } .onDidLoad { page in } .onWillUnload { page in } .onDidUnload { page in }

Güzel ve özlü değil mi? 🥲


Bonus kolaylık yöntemleri

alert(message: String) - doğrudan JS alert yöntemi

changePath(to: String) - URL yolunu değiştirme

HTML Öğeleri

Son olarak size HTML öğelerini nasıl(!) oluşturup kullanacağınızı anlatacağım!


Tüm HTML öğeleri, öznitelikleriyle birlikte Swift'te mevcuttur, tam liste örneğin MDN'dedir .


HTML öğelerinin yalnızca örnek bir kısa listesi:

SwifWeb kodu

HTML Kodu

Div()

<div></div>

H1(“text“)

<h1>text</h1>

A(“Click me“).href(““).target(.blank)

<a href=”” target=”_blank”>Click me</a>

Button(“Click“).onClick { print(“click“) }

<button onclick=”…”>Click</button>

InputText($text).placeholder("Title")

<input type=”text” placeholder=”title”>

InputCheckbox($checked)

<input type=”checkbox”>


Gördüğünüz gibi Swift'de herhangi bir HTML etiketine erişmek çok kolaydır çünkü girişler dışında hepsi aynı adla temsil edilir. Bunun nedeni, farklı girdi türlerinin farklı yöntemlere sahip olmasıdır ve ben bunları karıştırmak istemedim.


Basit Div

 Div()

bunun gibi tüm niteliklerine ve stil özelliklerine erişebiliriz

 Div().class(.myDivs) // <div class="myDivs"> .id(.myDiv) // <div id="myDiv"> .backgroundColor(.green) // <div style="background-color: green;"> .onClick { // adds `onClick` listener directly to the DOM element print("Clicked on div") } .attribute("key", "value") // <div key="value"> .attribute("boolKey", true, .trueFalse) // <div boolKey="true"> .attribute("boolKey", true, .yesNo) // <div boolKey="yes"> .attribute("checked", true, .keyAsValue) // <div checked="checked"> .attribute("muted", true, .keyWithoutValue) // <div muted> .custom("border", "2px solid red") // <div style="border: 2px solid red;">

Alt sınıflandırma

Stili önceden tanımlamak veya çok sayıda önceden tanımlanmış alt öğe ve dışarıda mevcut bazı uygun yöntemler ile bileşik bir öğe oluşturmak veya didAddToDOM ve didRemoveFromDOM gibi yaşam döngüsü olaylarını elde etmek için alt sınıf HTML öğesi.

Sadece bir Div olan ancak önceden tanımlanmış .divider sınıfına sahip bir Divider öğesi oluşturalım

 public class Divider: Div { // it is very important to override the name // because otherwise it will be <divider> in HTML open class override var name: String { "\(Div.self)".lowercased() } required public init() { super.init() } // this method executes immediately after any init method public override func postInit() { super.postInit() // here we are adding `divider` class self.class(.divider) } }

Alt sınıflandırma yaparken süper yöntemleri çağırmak çok önemlidir.

Bu olmadan beklenmedik davranışlarla karşılaşabilirsiniz.

DOM'a ekleme

Öğe, PageController'ın DOM'sine veya HTML öğesine hemen veya daha sonra eklenebilir.


Derhal

 Div { H1("Title") P("Subtitle") Div { Ul { Li("One") Li("Two") } } }


Veya daha sonra lazy var kullanarak

 lazy var myDiv1 = Div() lazy var myDiv2 = Div() Div { myDiv1 myDiv2 }

Böylece bir HTML öğesini önceden bildirebilir ve daha sonra istediğiniz zaman DOM'a ekleyebilirsiniz!

DOM'dan kaldırılıyor

 lazy var myDiv = Div() Div { myDiv } // somewhere later myDiv.remove()

Üst öğeye erişme

Herhangi bir HTML öğesinin, DOM'a eklenmesi durumunda üst öğeye erişim sağlayan isteğe bağlı bir denetleme özelliği vardır.

 Div().superview?.backgroundColor(.red)

if/else koşulları

Genellikle öğeleri yalnızca belirli koşullarda göstermemiz gerekir; bunun için if/else kullanalım.

 lazy var myDiv1 = Div() lazy var myDiv2 = Div() lazy var myDiv3 = Div() var myDiv4: Div? var showDiv2 = true Div { myDiv1 if showDiv2 { myDiv2 } else { myDiv3 } if let myDiv4 = myDiv4 { myDiv4 } else { P("Div 4 was nil") } }

Fakat reaktif değildir. showDiv2 false olarak ayarlamaya çalışırsanız hiçbir şey olmuyor.


Reaktif örnek

 lazy var myDiv1 = Div() lazy var myDiv2 = Div() lazy var myDiv3 = Div() @State var showDiv2 = true Div { myDiv1 myDiv2.hidden($showDiv2.map { !$0 }) // shows myDiv2 if showDiv2 == true myDiv3.hidden($showDiv2.map { $0 }) // shows myDiv3 if showDiv2 == false }


Neden $showDiv2.map {…} kullanmalıyız ?

Sırayla: çünkü SwiftUI değil. Kesinlikle.


Aşağıda @State hakkında daha fazla bilgi bulabilirsiniz .


Ham HTML

Ayrıca sayfaya veya HTML öğesine ham HTML eklemeniz gerekebilir ve bu kolayca mümkündür.

 Div { """ <a href="https://google.com">Go to Google</a> """ }


Her biri için

Statik örnek

 let names = ["Bob", "John", "Annie"] Div { ForEach(names) { name in Div(name) } // or ForEach(names) { index, name in Div("\(index). \(name)") } // or with range ForEach(1...20) { index in Div() } // and even like this 20.times { Div().class(.shootingStar) } }

Dinamik örnek

 @State var names = ["Bob", "John", "Annie"] Div { ForEach($names) { name in Div(name) } // or with index ForEach($names) { index, name in Div("\(index). \(name)") } } Button("Change 1").onClick { // this will append new Div with name automatically self.names.append("George") } Button("Change 2").onClick { // this will replace and update Divs with names automatically self.names = ["Bob", "Peppa", "George"] }

CSS

Yukarıdaki örneklerle aynı, ancak BuilderFunction da mevcut

 Stylesheet { ForEach(1...20) { index in CSSRule(Div.pointer.nthChild("\(index)")) // set rule properties depending on index } 20.times { index in CSSRule(Div.pointer.nthChild("\(index)")) // set rule properties depending on index } }


Aşağıdaki örnekte bir delay değeri gibi bazı değerleri yalnızca bir kez hesaplamak için ForEach döngülerinde BuilderFunction kullanabilirsiniz.

 ForEach(1...20) { index in BuilderFunction(9999.asRandomMax()) { delay in CSSRule(Pointer(".shooting_star").nthChild("\(index)")) .custom("top", "calc(50% - (\(400.asRandomMax() - 200)px))") .custom("left", "calc(50% - (\(300.asRandomMax() + 300)px))") .animationDelay(delay.ms) CSSRule(Pointer(".shooting_star").nthChild("\(index)").before) .animationDelay(delay.ms) CSSRule(Pointer(".shooting_star").nthChild("\(index)").after) .animationDelay(delay.ms) } }


Aynı zamanda bir argüman olarak da işlev görebilir

 BuilderFunction(calculate) { calculatedValue in // CSS rule or DOM element } func calculate() -> Int { return 1 + 1 }

BuilderFunction HTML öğeleri için de kullanılabilir :)

@State ile Reaktivite

@State günümüzde bildirimsel programlama için en çok arzu edilen şeydir.


Yukarıda da söylediğim gibi: SwiftUI değil, dolayısıyla her şeyi takip eden ve yeniden çizen bir küresel durum makinesi yok. Ve HTML öğeleri geçici yapılar değil sınıflardır, yani bunlar gerçek nesnelerdir ve onlara doğrudan erişebilirsiniz. Çok daha iyi ve esnektir, tüm kontrol sizdedir.

Kaputun altında ne var?

Değişiklikleri tüm abonelere bildiren bir özellik sarmalayıcıdır.

Değişikliklere nasıl abone olunur?

 enum Countries { case usa, australia, mexico } @State var selectedCounty: Countries = .usa $selectedCounty.listen { print("country changed") } $selectedCounty.listen { newValue in print("country changed to \(newValue)") } $selectedCounty.listen { oldValue, newValue in print("country changed from \(oldValue) to \(newValue)") }

HTML öğeleri değişikliklere nasıl tepki verebilir?

Basit metin örneği

 @State var text = "Hello world!" H1($text) // whenever text changes it updates inner-text in H1 InputText($text) // while user is typing text it updates $text which updates H1

Basit sayı örneği

 @State var height = 20.px Div().height($height) // whenever height var changes it updates height of the Div

Basit boole örneği

 @State var hidden = false Div().hidden($hidden) // whenever hidden changes it updates visibility of the Div

Haritalama örneği

 @State var isItCold = true H1($isItCold.map { $0 ? "It is cold 🥶" : "It is not cold 😌" })

İki eyaletin haritalanması

 @State var one = true @State var two = true Div().display($one.and($two).map { one, two in // returns .block if both one and two are true one && two ? .block : .none })

İkiden fazla eyaletin haritalanması

 @State var one = true @State var two = true @State var three = 15 Div().display($one.and($two).map { one, two in // returns true if both one and two are true one && two }.and($three).map { oneTwo, three in // here oneTwo is a result of the previous mapping // returns .block if oneTwo is true and three is 15 oneTwo && three == 15 ? .block : .none })

Tüm HTML ve CSS özellikleri @State değerlerini işleyebilir

Uzantılar

HTML öğelerini genişlet

Div gibi somut elemanlara bazı kullanışlı yöntemler ekleyebilirsiniz.

 extension Div { func makeItBeautiful() {} }

Veya ebeveyn class biliyorsanız öğe grupları.


Az sayıda ebeveyn sınıfı vardır.

BaseActiveStringElement - a , h1 vb. gibi dizeyle başlatılabilen öğeler içindir.

BaseContentElement - div , ul vb. gibi içinde içerik bulundurabilen tüm öğeler içindir.

BaseElement - tüm öğeler içindir


Yani tüm elemanların uzantısı bu şekilde yazılabilir

 extension BaseElement { func doSomething() {} }

Renkleri beyan edin

Renk sınıfı renklerden sorumludur. Önceden tanımlanmış HTML renkleri vardır, ancak kendi renklerinize de sahip olabilirsiniz.

 extension Color { var myColor1: Color { .hex(0xf1f1f1) } // which is 0xF1F1F1 var myColor2: Color { .hsl(60, 60, 60) } // which is hsl(60, 60, 60) var myColor3: Color { .hsla(60, 60, 60, 0.8) } // which is hsla(60, 60, 60, 0.8) var myColor4: Color { .rgb(60, 60, 60) } // which is rgb(60, 60, 60) var myColor5: Color { .rgba(60, 60, 60, 0.8) } // which is rgba(60, 60, 60, 0.8) }

Daha sonra H1(“Text“).color(.myColor1) gibi kullanın.

Sınıfları bildirin

 extension Class { var my: Class { "my" } }

Sonra bunu Div().class(.my) gibi kullanın

Kimlikleri bildir

 extension Id { var myId: Id { "my" } }

Daha sonra Div().id(.my) gibi kullanın

Web API'leri

Pencere

window nesnesi tamamen sarılmıştır ve App.current.window değişkeni aracılığıyla erişilebilir.

Tam referans MDN'de mevcuttur.


Aşağıda kısa bir genel bakış yapalım

Ön Plan Bayrağı

Bunu App.swift Lifecycle veya doğrudan bu şekilde dinleyebilirsiniz.

 App.current.window.$isInForeground.listen { isInForeground in // foreground flag changed }

veya istediğiniz zaman istediğiniz yerde okuyun

 if App.current.window.isInForeground { // do somethign }

veya HTML öğesiyle tepki verin

 Div().backgroundColor(App.current.window.$isInForeground.map { $0 ? .grey : .white })

Aktif Bayrak

Ön Plan bayrağıyla aynıdır ancak App.current.window.isActive aracılığıyla erişilebilir

Bir kullanıcının pencerenin içinde hâlâ etkileşimde olup olmadığını tespit eder.

Çevrimiçi durum

Ön Plan bayrağıyla aynıdır ancak App.current.window.isOnline aracılığıyla erişilebilir

Bir kullanıcının hala internete erişimi olup olmadığını tespit eder.

Karanlık mod durumu

Ön Plan bayrağıyla aynıdır ancak App.current.window.isDark aracılığıyla erişilebilir

Bir kullanıcının tarayıcısının veya işletim sisteminin karanlık modda olup olmadığını tespit eder.

İç ölçü

Kaydırma çubukları da dahil olmak üzere pencerenin içerik alanının (görüntü alanı) boyutu

App.current.window.innerSize , içindeki width ve height değerleri içindeki Size nesnesidir.

@State değişkeni olarak da mevcuttur.

Dış Boyut

Araç çubukları/kaydırma çubukları da dahil olmak üzere tarayıcı penceresinin boyutu.

App.current.window.outerSize , içindeki width ve height değerleri içindeki Size nesnesidir.

@State değişkeni olarak da mevcuttur.

Ekran

Geçerli pencerenin oluşturulduğu ekranın özelliklerini denetlemek için özel nesne. App.current.window.screen aracılığıyla kullanılabilir.

En ilginç özellik genellikle pixelRatio .

Tarih

Kullanıcı tarafından ziyaret edilen URL'leri içerir (tarayıcı penceresi içinde).

App.current.window.history veya sadece History.shared aracılığıyla edinilebilir.

@State değişkeni olarak erişilebilir olduğundan, gerekirse değişikliklerini dinleyebilirsiniz.

 App.current.window.$history.listen { history in // read history properties }

Basit değişken olarak da erişilebilir

 History.shared.length // size of the history stack History.shared.back() // to go back in history stack History.shared.forward() // to go forward in history stack History.shared.go(offset:) // going to specific index in history stack

Daha fazla ayrıntıyı MDN'de bulabilirsiniz.

Konum

Geçerli URL ile ilgili bilgileri içerir.

App.current.window.location veya yalnızca Location.shared aracılığıyla kullanılabilir.

@State değişkeni olarak erişilebilir olduğundan, gerekirse değişikliklerini dinleyebilirsiniz.

Örneğin yönlendirici bu şekilde çalışır.

 App.current.window.$location.listen { location in // read location properties }


Basit bir değişken olarak da erişilebilir

 Location.shared.href // also $href Location.shared.host // also $host Location.shared.port // also $port Location.shared.pathname // also $pathname Location.shared.search // also $search Location.shared.hash // also $hash

Daha fazla ayrıntıyı MDN'de bulabilirsiniz.

Gezgin

Tarayıcı hakkında bilgi içerir.

App.current.window.navigator veya sadece Navigator.shared aracılığıyla edinilebilir

En ilginç özellikler genellikle userAgent platform language cookieEnabled .

Yerel depolama

Anahtar/değer çiftlerinin bir web tarayıcısına kaydedilmesine olanak tanır. Son kullanma tarihi olmayan verileri saklar.

App.current.window.localStorage veya yalnızca LocalStorage.shared olarak mevcuttur.

 // You can save any value that can be represented in JavaScript LocalStorage.shared.set("key", "value") // saves String LocalStorage.shared.set("key", 123) // saves Int LocalStorage.shared.set("key", 0.8) // saves Double LocalStorage.shared.set("key", ["key":"value"]) // saves Dictionary LocalStorage.shared.set("key", ["v1", "v2"]) // saves Array // Getting values back LocalStorage.shared.string(forKey: "key") // returns String? LocalStorage.shared.integer(forKey: "key") // returns Int? LocalStorage.shared.string(forKey: "key") // returns String? LocalStorage.shared.value(forKey: "key") // returns JSValue? // Removing item LocalStorage.shared.removeItem(forKey: "key") // Removing all items LocalStorage.shared.clear()

Değişiklikleri izleme

 LocalStorage.onChange { key, oldValue, newValue in print("LocalStorage: key \(key) has been updated") }

Tüm öğelerin kaldırılmasını takip etme

 LocalStorage.onClear { print("LocalStorage: all items has been removed") }

Oturum Depolama

Anahtar/değer çiftlerinin bir web tarayıcısına kaydedilmesine olanak tanır. Yalnızca bir oturuma ait verileri depolar.

App.current.window.sessionStorage veya yalnızca SessionStorage.shared olarak mevcuttur.

API, yukarıda açıklanan LocalStorage ile tamamen aynıdır.

Belge

Tarayıcıya yüklenen herhangi bir web sayfasını temsil eder ve web sayfasının içeriğine giriş noktası görevi görür.

App.current.window.document aracılığıyla edinilebilir.

 App.current.window.document.title // also $title App.current.window.document.metaDescription // also $metaDescription App.current.window.document.head // <head> element App.current.window.document.body // <body> element App.current.window.documentquerySelector("#my") // returns BaseElement? App.current.window.document.querySelectorAll(".my") // returns [BaseElement]

Yerelleştirme

Statik yerelleştirme

Klasik yerelleştirme otomatiktir ve kullanıcının sistem diline bağlıdır

Nasıl kullanılır

 H1(String( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは")))

Dinamik yerelleştirme

Ekrandaki yerelleştirilmiş dizeleri anında değiştirmek istiyorsanız (sayfayı yeniden yüklemeden)

Mevcut dili arayarak değiştirebilirsiniz.

 Localization.current = .es

Kullanıcının dilini çerezlerde veya yerel depolamada bir yere kaydettiyseniz, bunu uygulama başlatılırken ayarlamanız gerekir.

 Lifecycle.didFinishLaunching { Localization.current = .es }

Nasıl kullanılır

 H1(LString( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは")))

Gelişmiş örnek

 H1(Localization.currentState.map { "Curent language: \($0.rawValue)" }) H2(LString(.en("English string"), .es("Hilo Español"))) Button("change lang").onClick { Localization.current = Localization.current.rawValue.contains("en") ? .es : .en }

Gidip getirmek

 import FetchAPI Fetch("https://jsonplaceholder.typicode.com/todos/1") { switch $0 { case .failure: break case .success(let response): print("response.code: \(response.status)") print("response.statusText: \(response.statusText)") print("response.ok: \(response.ok)") print("response.redirected: \(response.redirected)") print("response.headers: \(response.headers.dictionary)") struct Todo: Decodable { let id, userId: Int let title: String let completed: Bool } response.json(as: Todo.self) { switch $0 { case .failure(let error): break case .success(let todo): print("decoded todo: \(todo)") } } } }

XMLHttpRequest

 import XMLHttpRequest XMLHttpRequest() .open(method: "GET", url: "https://jsonplaceholder.typicode.com/todos/1") .onAbort { print("XHR onAbort") }.onLoad { print("XHR onLoad") }.onError { print("XHR onError") }.onTimeout { print("XHR onTimeout") }.onProgress{ progress in print("XHR onProgress") }.onLoadEnd { print("XHR onLoadEnd") }.onLoadStart { print("XHR onLoadStart") }.onReadyStateChange { readyState in print("XHR onReadyStateChange") } .send()

WebSocket

 import WebSocket let webSocket = WebSocket("wss://echo.websocket.org").onOpen { print("ws connected") }.onClose { (closeEvent: CloseEvent) in print("ws disconnected code: \(closeEvent.code) reason: \(closeEvent.reason)") }.onError { print("ws error") }.onMessage { message in print("ws message: \(message)") switch message.data { case .arrayBuffer(let arrayBuffer): break case .blob(let blob): break case .text(let text): break case .unknown(let jsValue): break } } Dispatch.asyncAfter(2) { // send as simple string webSocket.send("Hello from SwifWeb") // send as Blob webSocket.send(Blob("Hello from SwifWeb")) }

Konsol

Basit print(“Hello world“) JavaScript'teki console.log('Hello world') ile eşdeğerdir


Konsol yöntemleri de sevgiyle sarılmış ❤️

 Console.dir(...) Console.error(...) Console.warning(...) Console.clear()

Canlı önizleme

Canlı önizlemenin çalışmasını sağlamak için istediğiniz her dosyada WebPreview sınıfını bildirin.

 class IndexPage: PageController {} class Welcome_Preview: WebPreview { @Preview override class var content: Preview.Content { Language.en Title("Initial page") Size(640, 480) // add here as many elements as needed IndexPage() } }


Xcode

Lütfen depo sayfasındaki talimatları okuyun. Zor ama tamamen işe yarayan bir çözüm 😎


VSCode

VSCode içindeki Uzantılar'a gidin ve Webber'ı arayın.

Kurulduktan sonra Cmd+Shift+P tuşlarına basın (veya Linux/Windows'ta Ctrl+Shift+P )

Webber Live Preview bulun ve başlatın.

Sağ tarafta canlı önizleme penceresini göreceksiniz ve WebPreview sınıfını içeren dosyayı her kaydettiğinizde yenilenir.

JavaScript'e erişim

SwifWeb'in temeli olan JavaScriptKit aracılığıyla edinilebilir.

Resmi depoda nasıl yapılacağını okuyun.

Kaynaklar

Projenin içine css , js , png , jpg ve diğer statik kaynakları ekleyebilirsiniz.

Ancak bunları hata ayıklama sırasında veya son sürüm dosyalarında kullanılabilir hale getirmek için hepsini Package.swift'te bu şekilde bildirmeniz gerekir.

 .executableTarget(name: "App", dependencies: [ .product(name: "Web", package: "web") ], resources: [ .copy("css/*.css"), .copy("css"), .copy("images/*.jpg"), .copy("images/*.png"), .copy("images/*.svg"), .copy("images"), .copy("fonts/*.woff2"), .copy("fonts") ]),

Daha sonra bunlara örneğin şu şekilde erişebileceksiniz Img().src(“/images/logo.png“)

Hata ayıklama

Webber'ı aşağıdaki şekilde başlatın

webber serve sadece hızlı bir şekilde başlatmak için

webber serve -t pwa -s Service PWA modunda başlatmak için

Ek parametreler

Hata ayıklama amacıyla konsolda daha fazla bilgi göstermek için -v veya --verbose

-p 443 veya --port 443 webber sunucusunu varsayılan 8888 yerine 443 bağlantı noktasında başlatmak için

--browser chrome/safari istenen tarayıcıyı otomatik olarak açmak için, varsayılan olarak herhangi bir tarayıcıyı açmaz

--browser-self-signed hizmet çalışanlarının yerel hatalarını ayıklamak için gerekli, aksi halde çalışmazlar

--browser-incognito gizli modda ek tarayıcı örneğini açmak için, yalnızca Chrome ile çalışır


Uygulamanızı hata ayıklama modunda oluşturmak için, uygulamayı otomatik olarak Chrome'da açın ve herhangi bir dosyayı değiştirdiğinizde tarayıcıyı otomatik olarak yenileyin, uygulamayı bu şekilde başlatın

SPA için

webber serve --browser chrome

gerçek PWA testi için

webber serve -t pwa -s Service -p 443 --browser chrome --browser-self-signed --browser-incognito


İlk uygulama yükleme

İlk yükleme sürecini iyileştirmek isteyebilirsiniz.

Bunun için proje içindeki .webber/entrypoint/dev klasörünü açın ve index.html dosyasını düzenleyin.

Çok kullanışlı dinleyicilere sahip başlangıç HTML kodunu içerir: WASMLoadingStarted WASMLoadingStartedWithoutProgress WASMLoadingProgress WASMLoadingError .

Bu kodu, özel stilinizi uygulamak istediğiniz şekilde düzenlemekte özgürsünüz 🔥

Yeni uygulamayı tamamladığınızda, aynısını .webber/entrypoint/release klasörüne kaydetmeyi unutmayın.

Bina Yayını

PWA için webber release veya webber release -t pwa -s Service çalıştırmanız yeterlidir.

Daha sonra derlenmiş dosyaları .webber/release klasöründen alın ve sunucunuza yükleyin.

Nasıl dağıtılır

Dosyalarınızı herhangi bir statik hostinge yükleyebilirsiniz.

Hosting, wasm dosyaları için doğru İçerik Türünü sağlamalıdır!

Evet, wasm dosyaları için doğru Content-Type: application/wasm başlığına sahip olmak çok önemlidir, aksi halde ne yazık ki tarayıcı WebAssembly uygulamanızı yükleyemeyecektir.

Örneğin GithubPages, wasm dosyaları için doğru İçerik Türünü sağlamadığından ne yazık ki üzerinde WebAssembly sitelerini barındırmak imkansızdır.

Nginx

Nginx ile kendi sunucunuzu kullanıyorsanız /etc/nginx/mime.types açın ve application/wasm wasm; kayıt. Eğer evet ise o zaman gitmeye hazırsınız!

Çözüm

Umarım bugün sizi şaşırtmışımdır ve en azından SwifWeb'i deneyecek ve en fazla bir sonraki büyük web projeniz için kullanmaya başlayacaksınız!


Lütfen herhangi bir SwifWeb kütüphanesine katkıda bulunmaktan ve ayrıca hepsine ⭐️ yıldız eklemekten çekinmeyin!


Discord'da büyük bir destek bulabileceğiniz, küçük eğitimler okuyabileceğiniz ve gelecek güncellemelerden ilk önce haberdar olabileceğiniz harika bir SwiftStream topluluğum var! Seni aramızda görmek harika olurdu!


Bu sadece başlangıç; SwifWeb hakkında daha fazla makale için bizi takip etmeye devam edin!


Arkadaşlarına söyle!