[02] MyDay
MyDay is an application that shows school related information, details on students’ enrolled courses, as well as personal academic information.
I. Demo Video
II. GitHub
III. Application Icon
IV. Application Launch Screen (with Animation & Transition)
Notes: includes Information Property List, Custom Font
Additional feature: ".navigationBarTitleDisplayMode(.inline)"
Information Property List
Launch Screen → Background color
Home Page
a. Uses scale transition when image enters the screen with easeInOut animation when screen appears (onAppear).
b. The “welcome” text uses a custom font with bouncy animation, which causes its blinking effect, and clicking it would redirect to the main page.
NavigationStack{ //redirect to another view
VStack(spacing: 30.0){ //controls image
if show{
Image(.displayLogo)
.resizable()
.scaledToFit()
.transition(.scale)
}
}
.animation(.easeInOut(duration: 3), value: show)
.onAppear { //occur when entering view
show = true
}
NavigationLink{ //controls next view
MultipleTabView()
}label:{ //sets label which is clickable by the user
Text("welcome")
.foregroundStyle(.blue) //font color
.font(.custom( //custom font
"Bip Hope", fixedSize: 30
))
.animation(.bouncy(duration: 0.5).repeatForever(autoreverses:true).delay(3), value: show) //blinking effect
}
.navigationTitle("Home Page") //current view's title
.navigationBarTitleDisplayMode(.inline) //title's location on view
}
V. Custom Font (Additional Component)
Steps:
1. Download font with extension .otf or .ttf (there are plenty online).
2. Create a folder and insert font into the folder.
3. Add a new key “Fonts provided by application” in Info.plist and type in the custom fonts.
4. It could be applied on Text() with the following syntax:
Text("...")
.font(.custom("fontName", fixedSize: 10))
//Application
Text("welcome")
.foregroundStyle(.blue) //font color
.font(.custom("Bip Hope", fixedSize: 30))
//font size adjustment
Text("...")
.font(.system(size: 15))
VI. TabView
Additional feature: the use of enum and .tag(enum.xx)
1. There are three different pages: Home, Courses, Profile.
a. Home displays introduction and information upon the school.
b. Courses displays the students’ current enrolled courses.
c. Profile displays the students’ academic information.
2. NavigationStack and TabView
a. NavigationStack is used in switching views.
b. TabView is used in switching pages.
— Pair a page with a set of icon and text to differentiate pages.
— Gives a tag on each page to set its NavigationTitle.
— Set AccentColor in Assets to set its background color (dark&light mode).
enum Tabs: String{ //creates enum for each page
case Home
case Courses
case Profile
}
struct MultipleTabView: View {
@State var selectionTab: Tabs = .Home //set default enum
var body: some View {
NavigationStack{ //stack for navigation
TabView(selection: $selectionTab) { //redirecting between pages
HomePageView() //destination
.tabItem{
Label("Home", systemImage: "house") //clickable component
}
.tag(Tabs.Home) //page tag
ToDoList()//destination
.tabItem{
Label("Courses", systemImage: "text.book.closed.fill")//clickable component
}
.tag(Tabs.Courses)//page tag
ProfileView()//destination
.tabItem{
Label("Profile", systemImage: "person.crop.circle.fill")//clickable component
}
.tag(Tabs.Profile)//page tag
}
.navigationTitle(selectionTab.rawValue.capitalized)//page title
}
}
}
VII. ScrollView + LazyVGrid (+ LazyVStack, + LazyHStack)
Notes: includes LazyVStack (with Section), LazyHStack, clipShape()
Additional feature: ".multilineTextAlignment(.center)"
There are two types of ScrollView: horizontal and vertical
ScrollView(.vertical){ //scroll direction: vertical
/*content*/
ScrollView(.horizontal){ //scroll direction: horizontal
/*content*/
}
/*content*/
}
There are two types of LazyGrid: LazyVGrid and LazyHGrid
LazyVGrid(columns: [GridItem()){ //sets vertical grid
/*content: loops an array*/
}
LazyHGrid(rows: [GridItem()]{ //sets horizontal grid
/*content: loops an array*/
}
**There are two types of LazyStack: LazyVStack and LazyHStack
LazyVStack{ //sets vertical stack
/*content*/
LazyHStack{ //sets horizontal stack
/*content*/
}
/*content*/
}
/*content could contain "Section(header: Text(" "), content: { ... })"
Application
ScrollView(.vertical){ //scroll vertically
ScrollView(.horizontal){ //scroll horizontally
LazyHStack{ //sets horizontal stack
NavigationLink{...} //switch between pages
Link(destination: ..., label: {...}) //redirect to a URL link
Link(destination: ..., label: {...}) //redirect to a URL link
}
}
LazyVStack(...){ //sets vertical stack
LazyHStack(...){ //sets horizontal stack
Section(header: //divides into sections with a specified header
Text("卓越教學與特色研究兼具\n的海洋國際頂尖大學")
.font(.custom("SourceHanSerifTC-Bold", fixedSize: 25))
.padding(.horizontal, 30.0)
.multilineTextAlignment(.center) //sets multiline alignment orientation: middle
.background(.quaternary)
.clipShape(RoundedRectangle(cornerRadius: 20)), //clips based on a rounded rectangle shape
content: { //content of stack
LazyVGrid(columns: [GridItem()], content: { //sets a vertical grid
ForEach(pageContent, id : \.self) { par in //loops through an array
Text("\t" + par + "\n")
.font(.system(size: 15))
}
})
.padding(.top, -20.0)
.padding()
})
}
.frame(width:380, alignment: .center)
}
}
.onAppear(){...} //action when this page appears
}
VIII. NavigationStack and NavigationLink
NavigationStack is used when an application requires to switch between pages which is helped by NavigationLink that connects the two pages.
NavigationStack{
...
NavigationLink{
destination: /*DestinationView()*/, label:{
...
}
}
...
}
//Application
NavigationStack{ //creates stack
List{ //creates a list of objects
ForEach(courseTaken) {courses in //loops through an array
NavigationLink( //creates a link between current page and destination page
destination:ToDoDetailView(courseInfo:courses), //sets destination page
label:{ToDoRow(courseInfo:courses) //sets label of link
})
}
}
}
IX. Video Player (+ Music Player)
Note: contains Music Player
Video Player
- import AVKit
- Use a state variable to store AVPlayer(), which is a controller object of a media asset.
- Use VideoPlayer(player: player) to view the display content of a player.
- When entering page, set the state variable to a media file from a url .
import SwiftUI
import AVKit
struct VideoView: View {
@State var videoPlayer = AVPlayer()
var body: some View {
VStack{
VideoPlayer(player: videoPlayer)
.onAppear() {
videoPlayer = AVPlayer(url: Bundle.main.url(forResource: "video70YearsNTOU", withExtension: "mp4")!) //files exist in data storage
}
.frame(height:300)
Text("國立臺灣海洋大學70週年校慶")
}
}
}
Music Player
Note: result can be seen in the demo video.
- import AVKit
- Use a state variable to store AVPlayer(), which is a controller object of a media asset.
- Use a state variable to store boolean: the music is playing or not.
- Use VideoPlayer(player: player) to view the display content of a player.
- When entering page, set the state variable to a media file from a url and then replace the current item in the variable with the new media.
- Using “xx.play()” to play the music and “xx.pause()” to pause the music.
import SwiftUI
import AVKit
struct VideoView: View {
@State var musicPlayer = AVPlayer()
@State var isPlaying: Bool = false
var body: some View {
VStack{
...
//musicPlayer.pause() / musicPlayer.play()
}
.onAppear(){
if(!isPlaying){
let fileURL = Bundle.main.url(forResource:"backgroundMusic", withExtension: "mp3")!
let player = AVPlayerItem(url: fileURL)
musicPlayer.replaceCurrentItem(with: player)
}
}
}
}
X. Link
Link is used for navigating to a URL.
Link(destination: URL(string: "...")!, label:{...})
//Application
Link(destination: URL(string: "https://www.ntou.edu.tw")!, label: {
IconView(iconLogo: "logoNTOU", iconText: "學校網站")
})
XI. Extract Subview
Extracting a section of a program that is repetitive into a subview. This can be done by blocking the section of a certain program, right click on mouse and choose the option “Extract Subview”.
//subview called by the above program section
struct IconView: View {
let iconLogo:String
let iconText:String
var body: some View {
VStack(spacing: -10.0){
ZStack{
Circle()
.fill(.white)
.stroke(.black,lineWidth: 2)
.frame(width:150)
Image(iconLogo)
.resizable()
.scaledToFit()
.frame(height:100)
.frame(width:150,height:150)
.background(.clear)
.clipShape(Circle())
}
.padding()
Text(iconText)
.foregroundStyle(.blue)
.bold()
}
.padding(.bottom)
}
}
XII. List
List is a container that displays data as row(s) in a single column.
List{
ForEach(/*array*/){/*string as variable*/ in
...
}
}
// Application
List{
ForEach(courseTaken) {courses in
NavigationLink(
destination:ToDoDetailView(courseInfo:courses),label:{
ToDoRow(courseInfo:courses)
})
}
}
XIII. SF Symbols
SF Symbols is a collection of symbols that can be used for free in designing a view. Not only that, these symbols are also configurable.
Image(systemName: "name of symbol")
// Application
Image(systemName: "person.text.rectangle.fill")
.resizable()
.scaledToFit()
.frame(width:25, height:25)
.frame(width:40, height:40)
.background(.yellow)
.clipShape(Circle())
XIV. Dark Mode
There are two kinds of appearance modes: Light Mode and Dark Mode. Therefore, when designing an application, it is required to consider the display in both of the appearance modes.
XV. Date (Additional Feature)
Set Date
//time interval since 00:00:00 UTC on 1 January 2001
user.studentBirthday = Date(timeIntervalSinceReferenceDate: 63734400.0)
Converting Date to String
func dateToString(date : Date) -> String{
let dateFormatter = DateFormatter()
//date time string formatting
dateFormatter.dateFormat = "dd MMMM YYYY HH:mm"
return dateFormatter.string(from: date)
}
Calculating Age using Date
//calculate year differences to decide age
Calendar.current.dateComponents([.year], from: user.studentBirthday, to: Date.now).year!
Weekday: Sunday ~ Saturday
//looks for what weekday is today
Calendar.current.component(.weekday, from: Date.now)
XVI. Gradient Background (Additional Feature)
LinearGradient(gradient: Gradient(colors: [/*array of colors*/], startPoint: /*.orientation*/, endPoint: /*.orientation*/)
//Application
Image(displayImage)
.resizable()
.scaledToFill()
.transition(.opacity.animation(.bouncy))
.frame(width:200, height:200)
.frame(width:350)
.clipShape(Circle())
.background(LinearGradient(gradient: Gradient(colors: [.green, .blue]), startPoint: .leading, endPoint: .trailing)
.clipShape(Circle())
.frame(width: 215,height:215))
XVII. Multiple Style Text
Text("...")/*.modifier*/ + Text("...")/*.modifier*/
//Application
Text("年齡:")
.bold() +
Text("\(age)")
XVIII. Struct Data
Creates a struct data type which has relevant data type element.
import Foundation
struct Tasks: Identifiable{
let id = UUID() //universally unique identifier
let courseName: String
let courseDay: Int
let courseInstructor: String
let courseCredit: Int
let courseClass: String
}
extension Tasks{
static let defaultTask = Tasks(courseName: "無", courseDay: 1, courseInstructor: "無", courseCredit: 0, courseClass: "無")
}
XIX. 心得
Designing an application page is extremely difficult and time consuming, especially if the result isn't the same as what was expected. Learning many components and functions in a short amount of time is really tiring, but it will be of use in the future.
XX. Final Result