Commit 18d0db43 by Dmitriy Stepanets

- Finished forecast graph visualisation

- Added localized strings
- Working on sun animation
parent 4cf0cf20
......@@ -7,7 +7,7 @@
<key>1Weather.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
<integer>4</integer>
</dict>
<key>PG (Playground) 1.xcscheme</key>
<dict>
......
......@@ -51,7 +51,7 @@
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>4</integer>
<integer>0</integer>
</dict>
</dict>
</dict>
......
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
//
// NotificationName+Localization.swift
// 1Weather
//
// Created by Dmitry Stepanets on 18.02.2021.
//
import UIKit
import Localize_Swift
extension Notification.Name {
static let localizationChange = Notification.Name(LCLLanguageChangeNotification)
}
{
"images" : [
{
"filename" : "dewPoint.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
{
"images" : [
{
"filename" : "earth.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
{
"images" : [
{
"filename" : "humidity.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
{
"images" : [
{
"filename" : "pressure.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
{
"images" : [
{
"filename" : "sun.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
{
"images" : [
{
"filename" : "uv_index.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
{
"images" : [
{
"filename" : "visibility.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
......@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "255",
"green" : "255",
"red" : "255"
"blue" : "246",
"green" : "238",
"red" : "236"
}
},
"idiom" : "universal"
......@@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "255",
"green" : "255",
"red" : "255"
"blue" : "246",
"green" : "238",
"red" : "236"
}
},
"idiom" : "universal"
......
......@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "246",
"green" : "238",
"red" : "236"
"blue" : "255",
"green" : "255",
"red" : "255"
}
},
"idiom" : "universal"
......@@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "246",
"green" : "238",
"red" : "236"
"blue" : "255",
"green" : "255",
"red" : "255"
}
},
"idiom" : "universal"
......
/*
Localizable.strings
1Weather
Created by Dmitry Stepanets on 18.02.2021.
*/
//Forecast
"forecast.sunny" = "Sunny";
"forecast.clear" = "Clear";
"forecast.cloudy" = "Cloudy";
"forecast.partyCloudy" = "Partly Cloudy";
//Day time
"dayTime.morning" = "Morning";
"dayTime.noon" = "Noon";
"dayTime.evening" = "Evening";
"dayTime.night" = "Night";
//Condition
"condition.precipitation" = "Precipitation";
"condition.humidity" = "Humidity";
"condition.uvIndex" = "UV-Index";
"condition.pressure" = "Pressure";
"condition.dewPoint" = "Dew point";
"condition.visibility" = "Visibility";
//Forecast time period
"forecast.timePeriod.daily" = "Daily";
"forecast.timePeriod.hourly" = "Hourly";
"forecast.timePeriod.minutely" = "Minutely";
"forecast.timePeriod.now" = "now";
//Sun
"sun.title" = "sun";
"sun.maxUV" = "Max UV";
//Moon
"moon.title" = "moon";
"moon.newMoon" = "New Moon";
//Precipitation
"precipitation.title" = "precipitation";
//Next 24 hours
"next24.title" = "next 24 hours";
//Tab bar tabs
"tabBar.today" = "today";
"tabBar.forecast" = "forecast";
"tabBar.radar" = "radar";
"tabBar.menu" = "menu";
//
// ArrowButton.swift
// 1Weather
//
// Created by Dmitry Stepanets on 18.02.2021.
//
import UIKit
class ArrowButton: UIButton {
//Private
private let gradientBackground = GradientView(startColor: ThemeManager.currentTheme.segmentSelectedGradient[0],
endColor: ThemeManager.currentTheme.segmentSelectedGradient[1])
override init(frame: CGRect) {
super.init(frame: frame)
prepareButton()
prepareBackground()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MARK:- Prepare
private extension ArrowButton {
func prepareButton() {
let arrowImage = UIImage(named: "down_arrow")
setImage(arrowImage, for: .normal)
transform = CGAffineTransform.init(rotationAngle: -90 * .pi / 180)
tintColor = UIColor(hex: 0x161626)
clipsToBounds = false
layer.cornerRadius = 9
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.12
layer.shadowOffset = .init(width: 0, height: 3)
layer.shadowRadius = 8
}
func prepareBackground() {
gradientBackground.layer.cornerRadius = 9
insertSubview(gradientBackground, belowSubview: imageView!)
gradientBackground.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
}
}
//
// NeoButton.swift
// 1Weather
//
// Created by Dmitry Stepanets on 10.02.2021.
//
import UIKit
class NeoButton: UIButton {
//Private
private let lightColor = UIColor.white
private let darkColor = UIColor(hex: 0xe7e9fc)
private let backgroundLayer = CALayer()
private let highlightedGradientLayer = CAGradientLayer()
private let outerDarkShadowLayer = CALayer()
private let outerLightShadowLayer = CALayer()
private let borderGradient = CAGradientLayer()
private let borderLayer = CAShapeLayer()
private var cornerRadiusObserver:NSKeyValueObservation?
private var highlightedObserver:NSKeyValueObservation?
//MARK:- NewButton life cycle
init() {
super.init(frame: .zero)
prepareBackgroundLayer()
prepareHighlightedGradient()
prepareOuterShadows()
prepareBorderLayer()
prepareObservers()
}
override var backgroundColor: UIColor? {
get {
guard let color = backgroundLayer.backgroundColor else {
return nil
}
return UIColor(cgColor: color)
}
set {
self.backgroundLayer.backgroundColor = newValue?.cgColor
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundLayer.frame = self.bounds
self.borderGradient.frame = self.bounds
self.borderLayer.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
self.refreshShadows()
self.updateHighlightedGradient()
}
//Public
//Private
private func refreshShadows() {
outerLightShadowLayer.shadowPath = UIBezierPath(rect: self.bounds).cgPath
outerLightShadowLayer.masksToBounds = false
outerLightShadowLayer.shadowColor = lightColor.cgColor
outerLightShadowLayer.shadowOpacity = 0.5
outerLightShadowLayer.shadowOffset = .init(width: -10, height: -10)
outerLightShadowLayer.shadowRadius = 8
outerDarkShadowLayer.shadowPath = UIBezierPath(rect: self.bounds).cgPath
outerDarkShadowLayer.masksToBounds = false
outerDarkShadowLayer.shadowColor = darkColor.cgColor
outerDarkShadowLayer.shadowOpacity = 1
outerDarkShadowLayer.shadowOffset = .init(width: 6, height: 6)
outerDarkShadowLayer.shadowRadius = 8
}
private func updateHighlightedGradient() {
guard !self.bounds.isEmpty else { return }
self.highlightedGradientLayer.frame = self.bounds
let hypotenuse = sqrt(pow(bounds.size.width, 2) + pow(bounds.size.height, 2))
// area of square = height * width = hypotenuse * (maximum distance length perpendicular to hypotenuse)
let heightOfGradient = (bounds.size.width * bounds.size.height * 2) / hypotenuse
let angleInRadians = atan(bounds.size.height/bounds.size.width)
let newBounds = CGRect(x: bounds.size.width, y: bounds.size.height, width: hypotenuse, height: heightOfGradient)
self.highlightedGradientLayer.bounds = newBounds
self.highlightedGradientLayer.setAffineTransform(CGAffineTransform(rotationAngle: -angleInRadians))
}
}
private extension NeoButton {
private func prepareOuterShadows() {
layer.insertSublayer(outerLightShadowLayer, below: backgroundLayer)
layer.insertSublayer(outerDarkShadowLayer, below: backgroundLayer)
}
private func prepareBackgroundLayer() {
self.backgroundColor = .clear
self.backgroundLayer.masksToBounds = true
layer.insertSublayer(backgroundLayer, below: self.imageView?.layer)
}
private func prepareHighlightedGradient() {
// self.highlightedGradientLayer.colors = [ThemeManager.currentTheme.primaryButtonColor.darken(by: 0.1).cgColor,
// ThemeManager.currentTheme.primaryButtonColor.lighten(by: 0.04).cgColor]
self.highlightedGradientLayer.startPoint = .zero
self.highlightedGradientLayer.endPoint = .init(x: 0, y: 1.0)
self.highlightedGradientLayer.isHidden = false
backgroundLayer.addSublayer(self.highlightedGradientLayer)
}
private func prepareBorderLayer() {
borderGradient.colors = [UIColor.white.cgColor, UIColor.white.withAlphaComponent(0.15).cgColor]
borderLayer.lineWidth = 2 / UIScreen.main.scale
borderLayer.strokeColor = UIColor.black.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderGradient.mask = borderLayer
self.layer.addSublayer(borderGradient)
}
private func prepareObservers() {
cornerRadiusObserver = observe(\NeoButton.layer.cornerRadius, options: .new, changeHandler: { (button, change) in
self.backgroundLayer.cornerRadius = change.newValue ?? 0
self.borderGradient.cornerRadius = change.newValue ?? 0
})
highlightedObserver = observe(\NeoButton.isHighlighted, options: .new, changeHandler: { (button, change) in
guard let state = change.newValue else {
return
}
self.highlightedGradientLayer.isHidden = !state
})
}
}
......@@ -8,6 +8,11 @@
import UIKit
import BezierKit
private struct GraphDot {
let center:CGPoint
let shape:CAShapeLayer
}
class GraphView: UIView {
//Private
private let kIntersectAccuracy:CGFloat = 2
......@@ -15,14 +20,16 @@ class GraphView: UIView {
private let graphColor:UIColor
private let graphTintColor:UIColor
private let lineShape = CAShapeLayer()
private var lineDots = [GraphDot]()
private let cubicCurveAlgorithm = CubicCurveAlgorithm()
private var sections = [CubicCurve]()
private var currentPoints = [CGPoint]()
//MARK:- View life cycle
init(graphColor:UIColor, tintColor:UIColor) {
self.graphColor = graphColor
self.graphTintColor = tintColor
super.init(frame: .zero)
self.isUserInteractionEnabled = false
}
required init?(coder: NSCoder) {
......@@ -36,7 +43,7 @@ class GraphView: UIView {
$0.removeFromSuperlayer()
}
sections.removeAll()
currentPoints.removeAll()
lineDots.removeAll()
guard !points.isEmpty else { return }
......@@ -85,63 +92,46 @@ class GraphView: UIView {
let rightLine = LineSegment(p0: .init(x: endPointX - kIntersectAccuracy, y: 0),
p1: .init(x: endPointX - kIntersectAccuracy, y: self.bounds.height))
//Get all sections for the given tint
var intersectSections = [CubicCurve]()
//Get all sections for the given tint and cut them
let tintPath = UIBezierPath()
for section in sections {
if section.startingPoint.x >= leftLine.p0.x || section.endingPoint.x <= rightLine.p0.x {
intersectSections.append(section)
let maxLeftPointX = max(section.startingPoint.x, leftLine.startingPoint.x)
let minRightPointX = min(section.endingPoint.x, rightLine.endingPoint.x)
let leftBoundary = LineSegment(p0: .init(x: maxLeftPointX, y: 0),
p1: .init(x: maxLeftPointX, y: self.bounds.height))
let rightBoundary = LineSegment(p0: .init(x: minRightPointX, y: 0),
p1: .init(x: minRightPointX, y: self.bounds.height))
if let subcurve = getSubcurvePath(baseCurve: section,
leftBoundary: leftBoundary,
rightBoundary: rightBoundary)
{
tintPath.append(subcurve)
}
}
// if section.intersects(leftLine) && section.intersects(rightLine) {
// intersectSections.append(section)
// return
// }
//
// if section.intersects(leftLine) || section.intersects(rightLine) {
// intersectSections.append(section)
// }
}
let tintPath = UIBezierPath()
for intersectSection in intersectSections {
let leftBoundary = LineSegment(p0: .init(x: max(intersectSection.startingPoint.x, leftLine.startingPoint.x), y: 0),
p1: .init(x: max(intersectSection.startingPoint.x, leftLine.startingPoint.x), y: self.bounds.height))
let rightBoundary = LineSegment(p0: .init(x: min(intersectSection.endingPoint.x, rightLine.endingPoint.x), y: 0),
p1: .init(x: min(intersectSection.endingPoint.x, rightLine.endingPoint.x), y: self.bounds.height))
guard let subcurve = getSubcurvePath(baseCurve: intersectSection, leftBoundary: leftBoundary, rightBoundary: rightBoundary) else {
continue
}
tintPath.append(subcurve)
}
//Check for empty path
if tintPath.isEmpty { return }
// let leftIntersection = curves[0].intersections(with: leftLine)
// let rightIntersection = curves[1].intersections(with: rightLine)
//
// let leftPoint = curves[0].point(at: leftIntersection[0].t1)
// let rightPoint = curves[1].point(at: rightIntersection[0].t1)
// addDot(point: leftPoint)
// addDot(point: rightPoint)
//
// guard
// let leftSubcurvePath = getSubcurvePath(baseCurve: curves[0], leftBoundary: leftLine, rightBoundary: centerLine),
// let rightSubcurvePath = getSubcurvePath(baseCurve: curves[1], leftBoundary: centerLine, rightBoundary: rightLine)
// else {
// return
// }
//
// let tintPath = UIBezierPath()
// tintPath.append(leftSubcurvePath)
// tintPath.append(rightSubcurvePath)
//
let tintShape = CAShapeLayer()
tintShape.path = tintPath.cgPath
tintShape.fillColor = UIColor.clear.cgColor
tintShape.strokeColor = UIColor.black.cgColor
tintShape.strokeColor = ThemeManager.currentTheme.graphTintColor.cgColor
tintShape.lineWidth = 3
tintShape.lineCap = .round
tintShape.lineJoin = .round
layer.addSublayer(tintShape)
layer.insertSublayer(tintShape, at: 1)
}
public func tintDot(at: CGPoint) {
lineDots.forEach {
$0.shape.strokeColor = ThemeManager.currentTheme.graphColor.cgColor
}
guard let dotToTint = (lineDots.first{$0.center == at}) else { return }
dotToTint.shape.strokeColor = ThemeManager.currentTheme.graphTintColor.cgColor
}
//Private
......@@ -150,24 +140,25 @@ class GraphView: UIView {
let startPoint = CGPoint(x: 0, y: self.frame.height)
let endPoint = CGPoint(x: self.frame.width, y: self.frame.height)
currentPoints.append(startPoint)
currentPoints.append(contentsOf: points)
currentPoints.append(endPoint)
var pointsToAdd = [CGPoint]()
pointsToAdd.append(startPoint)
pointsToAdd.append(contentsOf: points)
pointsToAdd.append(endPoint)
path.move(to: currentPoints.first!)
if currentPoints.count == 2 {
path.move(to: pointsToAdd.first!)
if pointsToAdd.count == 2 {
path.addLine(to: points[1])
return path
}
let controlPoints = cubicCurveAlgorithm.controlPointsFromPoints(dataPoints: currentPoints)
for index in 1..<currentPoints.count {
sections.append(.init(p0: currentPoints[index - 1],
let controlPoints = cubicCurveAlgorithm.controlPointsFromPoints(dataPoints: pointsToAdd)
for index in 1..<pointsToAdd.count {
sections.append(.init(p0: pointsToAdd[index - 1],
p1: controlPoints[index - 1].controlPoint1,
p2: controlPoints[index - 1].controlPoint2,
p3: currentPoints[index]))
p3: pointsToAdd[index]))
path.addCurve(to: currentPoints[index],
path.addCurve(to: pointsToAdd[index],
controlPoint1: controlPoints[index - 1].controlPoint1,
controlPoint2: controlPoints[index - 1].controlPoint2)
}
......@@ -185,6 +176,7 @@ class GraphView: UIView {
dotShape.fillColor = UIColor.white.cgColor
dotShape.strokeColor = self.graphColor.cgColor
dotShape.lineWidth = 2
lineDots.append(.init(center: point, shape: dotShape))
layer.addSublayer(dotShape)
}
}
......@@ -69,7 +69,7 @@ struct DefaultTheme: ThemeProtocol {
var segmentSelectedGradient: [UIColor] {
let start = UIColor(named: "segment_selected_start_color") ?? .red
let end = UIColor(named: "segment_selected_end_color") ?? .red
return [end, start]
return [start, end]
}
var segmentBorderColor: UIColor {
......
......@@ -7,6 +7,15 @@
import UIKit
enum CityConditionButtonType: CaseIterable {
case precipitation
case humidity
case uvIndex
case pressure
case dewPoint
case visibility
}
class CityConditionButton: UIControl {
//Private
private let imageView = UIImageView()
......@@ -14,12 +23,46 @@ class CityConditionButton: UIControl {
private let nameLabel = UILabel()
private let descriptionLabel = UILabel()
init() {
init(type: CityConditionButtonType) {
super.init(frame: .zero)
prepareButton()
prepareImageView()
prepareLabels()
prepareImageView(type: type)
prepareLabels(type: type)
}
private func image(for type:CityConditionButtonType) -> UIImage? {
switch type {
case .precipitation:
return UIImage(named: "precipitation")
case .humidity:
return UIImage(named: "humidity")
case .uvIndex:
return UIImage(named: "uv_index")
case .pressure:
return UIImage(named: "pressure")
case .dewPoint:
return UIImage(named: "dewPoint")
case .visibility:
return UIImage(named: "visibility")
}
}
private func title(for type:CityConditionButtonType) -> String {
switch type {
case .precipitation:
return "condition.precipitation".localized()
case .humidity:
return "condition.humidity".localized()
case .uvIndex:
return "condition.uvIndex".localized()
case .pressure:
return "condition.pressure".localized()
case .dewPoint:
return "condition.dewPoint".localized()
case .visibility:
return "condition.visibility".localized()
}
}
required init?(coder: NSCoder) {
......@@ -39,11 +82,11 @@ private extension CityConditionButton {
self.layer.shadowRadius = 20
}
func prepareImageView() {
func prepareImageView(type: CityConditionButtonType) {
imageView.isUserInteractionEnabled = false
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.image = UIImage(named: "rain")
imageView.image = image(for: type)
addSubview(imageView)
imageView.snp.makeConstraints { (make) in
......@@ -52,7 +95,7 @@ private extension CityConditionButton {
}
}
func prepareLabels() {
func prepareLabels(type: CityConditionButtonType) {
//Value
valueLabel.isUserInteractionEnabled = false
valueLabel.font = AppFont.SFPro.bold(size: 18)
......@@ -71,7 +114,7 @@ private extension CityConditionButton {
nameLabel.font = AppFont.SFPro.regular(size: 16)
nameLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
nameLabel.textAlignment = .left
nameLabel.text = "Precipitation"
nameLabel.text = title(for: type)
addSubview(nameLabel)
nameLabel.snp.makeConstraints { (make) in
......
......@@ -69,9 +69,9 @@ private extension CityConditionsCell {
make.edges.height.equalToSuperview()
}
for _ in 0...5 {
let conditionButton = CityConditionButton()
stackView.addArrangedSubview(conditionButton)
for conditionType in CityConditionButtonType.allCases {
let button = CityConditionButton(type: conditionType)
stackView.addArrangedSubview(button)
}
}
......
......@@ -56,7 +56,7 @@ private extension CityForecastCell {
container.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(18)
make.top.equalToSuperview().inset(18)
make.bottom.equalToSuperview().inset(13)
make.bottom.equalToSuperview().inset(30)
}
}
......@@ -90,7 +90,7 @@ private extension CityForecastCell {
func prepareForecastLabel() {
forecastDescriptionLabel.font = AppFont.SFPro.bold(size: 16)
forecastDescriptionLabel.textColor = ThemeManager.currentTheme.primaryTextColor
forecastDescriptionLabel.text = "Partly Cloudy | 97°/89°"
forecastDescriptionLabel.text = "\("forecast.partyCloudy".localized()) | 97°/89°"
container.addSubview(forecastDescriptionLabel)
forecastDescriptionLabel.snp.makeConstraints { (make) in
......
......@@ -11,7 +11,9 @@ class CityForecastTimePeriodCell: UITableViewCell {
static let kIdentifier = "cityForecastTimePeriodCell"
//Private
private let periodSegmentedControl = ForecastTimePeriodControl(items: ["Daily", "Hourly", "Minutely"])
private let periodSegmentedControl = ForecastTimePeriodControl(items: ["forecast.timePeriod.daily".localized(),
"forecast.timePeriod.hourly".localized(),
"forecast.timePeriod.minutely".localized()])
private let scrollView = UIScrollView()
private let stackView = UIStackView()
private let summaryView = UIView()
......@@ -42,15 +44,22 @@ class CityForecastTimePeriodCell: UITableViewCell {
}
private func drawGraph() {
guard let periodButton = stackView.arrangedSubviews.first as? PeriodForecastButton else { return }
let graphRect = periodButton.getGraphRect()
guard graphRect.width > 0 else { return }
graphView.frame = .init(x: 0, y: graphRect.origin.y, width: scrollView.contentSize.width, height: graphRect.height)
//Draw points and lines
self.graphView.drawGraph(with: self.getGraphPoints())
graphView.tintGraphFrom(startPointX: 100,
endPointX: scrollView.contentSize.width - 150)
guard let periodButtons = stackView.arrangedSubviews as? [PeriodForecastButton] else { return }
for (index, button) in periodButtons.enumerated() {
if button.isSelected {
let graphRect = button.getGraphRect()
guard graphRect.width > 0 else { return }
graphView.frame = .init(x: 0, y: graphRect.origin.y, width: scrollView.contentSize.width, height: graphRect.height)
//Draw points and lines
let graphPoints = self.getGraphPoints()
self.graphView.drawGraph(with: graphPoints)
self.graphView.tintGraphFrom(startPointX: button.frame.origin.x,
endPointX: button.frame.origin.x + button.bounds.width)
self.graphView.tintDot(at: graphPoints[index])
return
}
}
}
private func getGraphPoints() -> [CGPoint] {
......@@ -76,6 +85,15 @@ class CityForecastTimePeriodCell: UITableViewCell {
return points
}
@objc private func handleConditionButton(button: PeriodForecastButton) {
stackView.arrangedSubviews.forEach {
if let periodButton = $0 as? PeriodForecastButton {
periodButton.isSelected = periodButton === button
}
}
drawGraph()
}
}
private extension CityForecastTimePeriodCell {
......@@ -98,6 +116,7 @@ private extension CityForecastTimePeriodCell {
func prepareScrollView() {
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.clipsToBounds = false
contentView.addSubview(scrollView)
scrollView.snp.makeConstraints { (make) in
......@@ -122,8 +141,10 @@ private extension CityForecastTimePeriodCell {
make.edges.height.equalToSuperview()
}
for _ in 0..<12 {
for index in 0..<12 {
let conditionButton = PeriodForecastButton()
conditionButton.addTarget(self, action: #selector(handleConditionButton(button:)), for: .touchUpInside)
conditionButton.isSelected = index == 1
stackView.addArrangedSubview(conditionButton)
}
}
......
......@@ -29,6 +29,32 @@ class PeriodForecastButton: UIControl {
fatalError("init(coder:) has not been implemented")
}
override var isSelected: Bool {
didSet {
if isSelected {
self.backgroundColor = UIColor.white
self.tempLabel.font = AppFont.SFPro.bold(size: 16)
self.timeLabel.font = AppFont.SFPro.bold(size: 12)
self.indicatorImageView.alpha = 1.0
self.layer.shadowColor = UIColor(hex: 0xe5e6f4).cgColor
self.layer.shadowOffset = .init(width: 5, height: 5)
self.layer.shadowRadius = 18
self.layer.shadowOpacity = 0.64
}
else {
self.backgroundColor = UIColor.white.withAlphaComponent(0.5)
self.tempLabel.font = AppFont.SFPro.regular(size: 16)
self.timeLabel.font = AppFont.SFPro.regular(size: 12)
self.indicatorImageView.alpha = 0.5
self.layer.shadowColor = UIColor.clear.cgColor
self.layer.shadowOffset = .zero
self.layer.shadowRadius = 0
self.layer.shadowOpacity = 0
}
}
}
//Public
public func getGraphRect() -> CGRect {
let topInset = self.tempLabel.frame.origin.y + self.tempLabel.frame.size.height + kGraphInset
......@@ -41,6 +67,7 @@ class PeriodForecastButton: UIControl {
private extension PeriodForecastButton {
func prepareButton() {
clipsToBounds = false
backgroundColor = UIColor.white
layer.cornerRadius = 12
layer.borderColor = UIColor(hex: 0xeceef6).cgColor
......
//
// CitySunCell.swift
// 1Weather
//
// Created by Dmitry Stepanets on 18.02.2021.
//
import UIKit
class CitySunCell: UITableViewCell {
static let kIdentifier = "citySunCell"
//Private
private let headingLabel = UILabel()
private let arrowButton = ArrowButton()
private let infographicContainer = UIView()
private let infographicLabel = UILabel()
private let infographicDashLine = CAShapeLayer()
private let earthImageView = UIImageView(image: UIImage(named: "earth"))
private let sunImageView = UIImageView(image: UIImage(named: "sun"))
private let sunActivityContainer = UIView()
private let sunActivityStartLabel = UILabel()
private let sunActivityEndLabel = UILabel()
private let maxUvLabel = UILabel()
//Computed
private var dashLinePath:CGPath?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
prepareCell()
prepareHeading()
prepareInfographicContainer()
prepareSunActivityContainer()
}
override func layoutSubviews() {
super.layoutSubviews()
drawDashLine()
updateSunPosition()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func drawDashLine() {
let dashHeight = earthImageView.bounds.height + 32
let dashLineRect = CGRect(x: 30,
y: earthImageView.frame.origin.y - 16,
width: infographicContainer.frame.width - 60,
height: dashHeight * 2)
dashLinePath = UIBezierPath(ovalIn: dashLineRect).cgPath
//Mask
let maskLayer = CAShapeLayer()
let maskPath = UIBezierPath(rect: CGRect(x: dashLineRect.origin.x, y: dashLineRect.origin.y,
width: dashLineRect.width, height: dashLineRect.height / 2 - 16))
maskLayer.path = maskPath.cgPath
maskLayer.fillRule = .evenOdd
infographicDashLine.path = dashLinePath
infographicDashLine.mask = maskLayer
}
private func updateSunPosition() {
sunImageView.frame.origin = .init(x: 36 - sunImageView.frame.width/2,
y: infographicContainer.frame.height - sunImageView.frame.height)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = self.dashLinePath
animation.calculationMode = .paced
animation.duration = 5
animation.rotationMode = .rotateAuto
sunImageView.layer.add(animation, forKey: nil)
}
@objc private func handleArrowButton() {
}
}
//MARK:- Prepare
private extension CitySunCell {
func prepareCell() {
selectionStyle = .none
contentView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
}
func prepareHeading() {
//Label
headingLabel.font = AppFont.SFPro.bold(size: 16)
headingLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
headingLabel.text = "sun.title".localized().uppercased()
contentView.addSubview(headingLabel)
headingLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(18)
make.top.equalToSuperview().inset(15)
}
//Arrow button
arrowButton.addTarget(self, action: #selector(handleArrowButton), for: .touchUpInside)
contentView.addSubview(arrowButton)
arrowButton.snp.makeConstraints { (make) in
make.width.height.equalTo(30)
make.right.equalToSuperview().inset(18)
make.centerY.equalTo(headingLabel)
}
}
func prepareInfographicContainer() {
//Container
infographicContainer.backgroundColor = UIColor(hex: 0xECEEF6)
infographicContainer.layer.cornerRadius = 12
contentView.addSubview(infographicContainer)
infographicContainer.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(18)
make.top.equalTo(headingLabel.snp.bottom).offset(18)
make.height.equalTo(212)
}
//Label
infographicLabel.lineBreakMode = .byWordWrapping
infographicLabel.numberOfLines = 0
infographicLabel.font = AppFont.SFPro.regular(size: 16)
infographicLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
infographicLabel.text = "Reduce time in the sun between 10 a.m and 4 p.m"
infographicContainer.addSubview(infographicLabel)
infographicLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(18)
make.top.equalToSuperview().inset(18)
make.right.equalToSuperview().inset(18)
}
//Earth
earthImageView.contentMode = .scaleAspectFit
infographicContainer.addSubview(earthImageView)
earthImageView.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(18)
make.bottom.equalToSuperview()
}
//Dash line
infographicDashLine.strokeColor = UIColor(hex: 0x212334).cgColor
infographicDashLine.fillColor = UIColor.clear.cgColor
infographicDashLine.lineWidth = 1 / UIScreen.main.scale
infographicDashLine.lineDashPattern = [4,2]
infographicContainer.layer.addSublayer(infographicDashLine)
//Sun
sunImageView.frame = .init(origin: .zero, size: CGSize(width: 28, height: 28))
if let path = dashLinePath {
sunImageView.center = path.currentPoint
}
sunImageView.contentMode = .scaleAspectFit
infographicContainer.addSubview(sunImageView)
}
func prepareSunActivityContainer() {
sunActivityContainer.clipsToBounds = false
sunActivityContainer.backgroundColor = .white
sunActivityContainer.layer.cornerRadius = 12
sunActivityContainer.layer.shadowColor = UIColor(hex: 0xe5e6f4).cgColor
sunActivityContainer.layer.shadowRadius = 18
sunActivityContainer.layer.shadowOffset = .init(width: 5, height: 5)
sunActivityContainer.layer.shadowOpacity = 0.64
contentView.addSubview(sunActivityContainer)
sunActivityContainer.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(18)
make.top.equalTo(infographicContainer.snp.bottom).offset(10)
make.height.equalTo(100)
make.bottom.equalToSuperview().inset(15)
}
}
}
......@@ -46,7 +46,8 @@ private extension TodayAdCell {
container.snp.makeConstraints { (make) in
make.height.equalTo(64)
make.top.bottom.equalToSuperview().inset(17)
make.top.equalToSuperview()
make.bottom.equalToSuperview().inset(18)
make.left.right.equalToSuperview().inset(18)
}
}
......
......@@ -12,13 +12,20 @@ private enum TodayTableCell {
case ad
case conditions
case forecastPeriod
case sun
}
class TodayViewController: UIViewController {
//Private
private let tableView = UITableView()
private let tableCells:[TodayTableCell] = [.forecast, .ad, .conditions, .forecastPeriod]
private let tableCells:[TodayTableCell] = [.forecast, .ad, .conditions, .forecastPeriod, .sun]
private var localizationObserver:Any?
deinit {
if let observer = localizationObserver {
NotificationCenter.default.removeObserver(observer)
}
}
override func viewDidLoad() {
super.viewDidLoad()
......@@ -26,7 +33,7 @@ class TodayViewController: UIViewController {
prepareNavigationBar()
prepareTableView()
}
@objc private func handleCityButton() {
print("Handle city button")
}
......@@ -39,6 +46,10 @@ class TodayViewController: UIViewController {
private extension TodayViewController {
func prepareViewController() {
view.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
localizationObserver = NotificationCenter.default.addObserver(forName: .localizationChange, object: nil, queue: .main, using: { _ in
self.tableView.reloadData()
})
}
func prepareNavigationBar() {
......@@ -68,10 +79,11 @@ private extension TodayViewController {
tableView.register(TodayAdCell.self, forCellReuseIdentifier: TodayAdCell.kIdentifier)
tableView.register(CityConditionsCell.self, forCellReuseIdentifier: CityConditionsCell.kIdentifier)
tableView.register(CityForecastTimePeriodCell.self, forCellReuseIdentifier: CityForecastTimePeriodCell.kIdentifier)
tableView.register(CitySunCell.self, forCellReuseIdentifier: CitySunCell.kIdentifier)
tableView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
tableView.separatorStyle = .none
tableView.tableFooterView = UIView()
tableView.estimatedRowHeight = 100
tableView.estimatedRowHeight = UITableView.automaticDimension
tableView.rowHeight = UITableView.automaticDimension
tableView.delegate = self
tableView.dataSource = self
......@@ -103,6 +115,9 @@ extension TodayViewController: UITableViewDataSource {
case .forecastPeriod:
let cell = tableView.dequeueReusableCell(withIdentifier: CityForecastTimePeriodCell.kIdentifier, for: indexPath) as! CityForecastTimePeriodCell
return cell
case .sun:
let cell = tableView.dequeueReusableCell(withIdentifier: CitySunCell.kIdentifier, for: indexPath) as! CitySunCell
return cell
}
}
}
......
......@@ -8,4 +8,5 @@ target '1Weather' do
# Pods for 1Weather
pod 'SnapKit'
pod 'BezierKit'
pod 'Localize-Swift'
end
PODS:
- BezierKit (0.10.0)
- Localize-Swift (3.2.0):
- Localize-Swift/LocalizeSwiftCore (= 3.2.0)
- Localize-Swift/UIKit (= 3.2.0)
- Localize-Swift/LocalizeSwiftCore (3.2.0)
- Localize-Swift/UIKit (3.2.0):
- Localize-Swift/LocalizeSwiftCore
- SnapKit (5.0.1)
DEPENDENCIES:
- BezierKit
- Localize-Swift
- SnapKit
SPEC REPOS:
trunk:
- BezierKit
- Localize-Swift
- SnapKit
SPEC CHECKSUMS:
BezierKit: 1828aca1675d68f0659c5353bc5b0d3399a3910c
Localize-Swift: 6f4475136bdb0d7b2882ea3d4ea919d70142b232
SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
PODFILE CHECKSUM: 9a72121885af918e1ac484fb10d514827acb4fbc
PODFILE CHECKSUM: 06893793de13524c28b9afdc6dca649b77af3601
COCOAPODS: 1.10.1
Copyright (c) 2015 Roy Marmelstein (http://roysapps.com/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
[![Platform](https://img.shields.io/cocoapods/p/Localize-Swift.svg?maxAge=2592000)](http://cocoapods.org/?q=Localize-Swift)
[![Version](http://img.shields.io/cocoapods/v/Localize-Swift.svg)](http://cocoapods.org/?q=Localize-Swift)
[![Build Status](https://travis-ci.org/marmelroy/Localize-Swift.svg?branch=master)](https://travis-ci.org/marmelroy/Localize-Swift)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
# Localize-Swift
Localize-Swift is a simple framework that improves i18n and localization in Swift iOS apps - providing cleaner syntax and in-app language switching.
<p align="center"><img src="http://i.imgur.com/vsrpqBt.gif" width="242" height="425"/></p>
## Features
- Keep the Localizable.strings file your app already uses.
- Allow your users to change the app's language without changing their device language.
- Use .localized() instead of NSLocalizedString(key,comment) - a more Swifty syntax.
- Generate your strings with a new genstrings swift/python script that recognises .localized().
## Usage
Import Localize at the top of each Swift file that will contain localized text.
If CocoaPods -
```swift
import Localize_Swift
```
Add `.localized()` following any `String` object you want translated:
```swift
textLabel.text = "Hello World".localized()
```
To get an array of available localizations:
```swift
Localize.availableLanguages()
```
To change the current language:
```swift
Localize.setCurrentLanguage("fr")
```
To update the UI in the view controller where a language change can take place, observe LCLLanguageChangeNotification:
```swift
NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil)
```
To reset back to the default app language:
```swift
Localize.resetCurrentLanguageToDefault()
```
## genstrings
To support this new i18n syntax, Localize-Swift includes custom genstrings swift script.
Copy the genstrings.swift file into your project's root folder and run with
```bash
./genstrings.swift
```
This will print the collected strings in the terminal. Select and copy to your default Localizable.strings.
The script includes the ability to specify excluded directories and files (by editing the script).
### [Preferrred] Setting up with [Swift Package Manager](https://swiftpm.co/?query=Localize-Swift)
The [Swift Package Manager](https://swift.org/package-manager/) is now the preferred tool for distributing Localize-Swift.
From Xcode 11+ :
1. Select File > Swift Packages > Add Package Dependency. Enter `https://github.com/marmelroy/Localize-Swift.git` in the "Choose Package Repository" dialog.
2. In the next page, specify the version resolving rule as "Up to Next Major" with "3.2.0".
3. After Xcode checked out the source and resolving the version, you can choose the "Localize-Swift" library and add it to your app target.
For more info, read [Adding Package Dependencies to Your App](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) from Apple.
Alternatively, you can also add Localize-Swift to your `Package.swift` file:
```swift
dependencies: [
.package(url: "https://github.com/marmelroy/Localize-Swift.git", .upToNextMajor(from: "3.2.0"))
]
```
### Setting up with Carthage
[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.
You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
```bash
$ brew update
$ brew install carthage
```
To integrate Localize-Swift into your Xcode project using Carthage, specify it in your `Cartfile`:
```ogdl
github "marmelroy/Localize-Swift"
```
### Setting up with [CocoaPods](http://cocoapods.org/?q=Localize-Swift)
```ruby
source 'https://github.com/CocoaPods/Specs.git'
pod 'Localize-Swift', '~> 3.2'
```
//
// Localize.swift
// Localize
//
// Created by Roy Marmelstein on 05/08/2015.
// Copyright © 2015 Roy Marmelstein. All rights reserved.
//
import Foundation
/// Internal current language key
let LCLCurrentLanguageKey = "LCLCurrentLanguageKey"
/// Default language. English. If English is unavailable defaults to base localization.
let LCLDefaultLanguage = "en"
/// Base bundle as fallback.
let LCLBaseBundle = "Base"
/// Name for language change notification
public let LCLLanguageChangeNotification = "LCLLanguageChangeNotification"
// MARK: Localization Syntax
/**
Swift 1.x friendly localization syntax, replaces NSLocalizedString
- Parameter string: Key to be localized.
- Returns: The localized string.
*/
public func Localized(_ string: String) -> String {
return string.localized()
}
/**
Swift 1.x friendly localization syntax with format arguments, replaces String(format:NSLocalizedString)
- Parameter string: Key to be localized.
- Returns: The formatted localized string with arguments.
*/
public func Localized(_ string: String, arguments: CVarArg...) -> String {
return String(format: string.localized(), arguments: arguments)
}
/**
Swift 1.x friendly plural localization syntax with a format argument
- parameter string: String to be formatted
- parameter argument: Argument to determine pluralisation
- returns: Pluralized localized string.
*/
public func LocalizedPlural(_ string: String, argument: CVarArg) -> String {
return string.localizedPlural(argument)
}
public extension String {
/**
Swift 2 friendly localization syntax, replaces NSLocalizedString
- Returns: The localized string.
*/
func localized() -> String {
return localized(using: nil, in: .main)
}
/**
Swift 2 friendly localization syntax with format arguments, replaces String(format:NSLocalizedString)
- Returns: The formatted localized string with arguments.
*/
func localizedFormat(_ arguments: CVarArg...) -> String {
return String(format: localized(), arguments: arguments)
}
/**
Swift 2 friendly plural localization syntax with a format argument
- parameter argument: Argument to determine pluralisation
- returns: Pluralized localized string.
*/
func localizedPlural(_ argument: CVarArg) -> String {
return NSString.localizedStringWithFormat(localized() as NSString, argument) as String
}
/**
Add comment for NSLocalizedString
- Returns: The localized string.
*/
func commented(_ argument: String) -> String {
return self
}
}
// MARK: Language Setting Functions
open class Localize: NSObject {
/**
List available languages
- Returns: Array of available languages.
*/
open class func availableLanguages(_ excludeBase: Bool = false) -> [String] {
var availableLanguages = Bundle.main.localizations
// If excludeBase = true, don't include "Base" in available languages
if let indexOfBase = availableLanguages.firstIndex(of: "Base") , excludeBase == true {
availableLanguages.remove(at: indexOfBase)
}
return availableLanguages
}
/**
Current language
- Returns: The current language. String.
*/
open class func currentLanguage() -> String {
if let currentLanguage = UserDefaults.standard.object(forKey: LCLCurrentLanguageKey) as? String {
return currentLanguage
}
return defaultLanguage()
}
/**
Change the current language
- Parameter language: Desired language.
*/
open class func setCurrentLanguage(_ language: String) {
let selectedLanguage = availableLanguages().contains(language) ? language : defaultLanguage()
if (selectedLanguage != currentLanguage()){
UserDefaults.standard.set(selectedLanguage, forKey: LCLCurrentLanguageKey)
UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: Notification.Name(rawValue: LCLLanguageChangeNotification), object: nil)
}
}
/**
Default language
- Returns: The app's default language. String.
*/
open class func defaultLanguage() -> String {
var defaultLanguage: String = String()
guard let preferredLanguage = Bundle.main.preferredLocalizations.first else {
return LCLDefaultLanguage
}
let availableLanguages: [String] = self.availableLanguages()
if (availableLanguages.contains(preferredLanguage)) {
defaultLanguage = preferredLanguage
}
else {
defaultLanguage = LCLDefaultLanguage
}
return defaultLanguage
}
/**
Resets the current language to the default
*/
open class func resetCurrentLanguageToDefault() {
setCurrentLanguage(self.defaultLanguage())
}
/**
Get the current language's display name for a language.
- Parameter language: Desired language.
- Returns: The localized string.
*/
open class func displayNameForLanguage(_ language: String) -> String {
let locale : NSLocale = NSLocale(localeIdentifier: currentLanguage())
if let displayName = locale.displayName(forKey: NSLocale.Key.identifier, value: language) {
return displayName
}
return String()
}
}
//
// Localize_Swift.h
// Localize_Swift
//
// Created by Roy Marmelstein on 21/01/2016.
// Copyright © 2020 Roy Marmelstein. All rights reserved.
//
@import Foundation;
//! Project version number for Localize_Swift.
FOUNDATION_EXPORT double Localize_SwiftVersionNumber;
//! Project version string for Localize_Swift.
FOUNDATION_EXPORT const unsigned char Localize_SwiftVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Localize_Swift/PublicHeader.h>
//
// String+LocalizeBundle.swift
// Localize_Swift
//
// Copyright © 2020 Roy Marmelstein. All rights reserved.
//
import Foundation
/// bundle friendly extension
public extension String {
/**
Swift 2 friendly localization syntax, replaces NSLocalizedString.
- parameter bundle: The receiver’s bundle to search. If bundle is `nil`,
the method attempts to use main bundle.
- returns: The localized string.
*/
func localized(in bundle: Bundle?) -> String {
return localized(using: nil, in: bundle)
}
/**
Swift 2 friendly localization syntax with format arguments, replaces String(format:NSLocalizedString).
- parameter arguments: arguments values for temlpate (substituted according to the user’s default locale).
- parameter bundle: The receiver’s bundle to search. If bundle is `nil`,
the method attempts to use main bundle.
- returns: The formatted localized string with arguments.
*/
func localizedFormat(arguments: CVarArg..., in bundle: Bundle?) -> String {
return String(format: localized(in: bundle), arguments: arguments)
}
/**
Swift 2 friendly plural localization syntax with a format argument.
- parameter argument: Argument to determine pluralisation.
- parameter bundle: The receiver’s bundle to search. If bundle is `nil`,
the method attempts to use main bundle.
- returns: Pluralized localized string.
*/
func localizedPlural(argument: CVarArg, in bundle: Bundle?) -> String {
return NSString.localizedStringWithFormat(localized(in: bundle) as NSString, argument) as String
}
}
//
// String+LocalizeTableName.swift
// Localize_Swift
//
// Copyright © 2020 Vitalii Budnik. All rights reserved.
//
import Foundation
/// tableName friendly extension
public extension String {
/**
Swift 2 friendly localization syntax, replaces NSLocalizedString.
- parameter tableName: The receiver’s string table to search. If tableName is `nil`
or is an empty string, the method attempts to use `Localizable.strings`.
- returns: The localized string.
*/
func localized(using tableName: String?) -> String {
return localized(using: tableName, in: .main)
}
/**
Swift 2 friendly localization syntax with format arguments, replaces String(format:NSLocalizedString).
- parameter arguments: arguments values for temlpate (substituted according to the user’s default locale).
- parameter tableName: The receiver’s string table to search. If tableName is `nil`
or is an empty string, the method attempts to use `Localizable.strings`.
- returns: The formatted localized string with arguments.
*/
func localizedFormat(arguments: CVarArg..., using tableName: String?) -> String {
return String(format: localized(using: tableName), arguments: arguments)
}
/**
Swift 2 friendly plural localization syntax with a format argument.
- parameter argument: Argument to determine pluralisation.
- parameter tableName: The receiver’s string table to search. If tableName is `nil`
or is an empty string, the method attempts to use `Localizable.strings`.
- returns: Pluralized localized string.
*/
func localizedPlural(argument: CVarArg, using tableName: String?) -> String {
return NSString.localizedStringWithFormat(localized(using: tableName) as NSString, argument) as String
}
}
//
// String+LocalizedBundleTableName.swift
// Localize_Swift
//
// Copyright © 2020 Roy Marmelstein. All rights reserved.
//
import Foundation
/// bundle & tableName friendly extension
public extension String {
/**
Swift 2 friendly localization syntax, replaces NSLocalizedString.
- parameter tableName: The receiver’s string table to search. If tableName is `nil`
or is an empty string, the method attempts to use `Localizable.strings`.
- parameter bundle: The receiver’s bundle to search. If bundle is `nil`,
the method attempts to use main bundle.
- returns: The localized string.
*/
func localized(using tableName: String?, in bundle: Bundle?) -> String {
let bundle: Bundle = bundle ?? .main
if let path = bundle.path(forResource: Localize.currentLanguage(), ofType: "lproj"),
let bundle = Bundle(path: path) {
return bundle.localizedString(forKey: self, value: nil, table: tableName)
}
else if let path = bundle.path(forResource: LCLBaseBundle, ofType: "lproj"),
let bundle = Bundle(path: path) {
return bundle.localizedString(forKey: self, value: nil, table: tableName)
}
return self
}
/**
Swift 2 friendly localization syntax with format arguments, replaces String(format:NSLocalizedString).
- parameter arguments: arguments values for temlpate (substituted according to the user’s default locale).
- parameter tableName: The receiver’s string table to search. If tableName is `nil`
or is an empty string, the method attempts to use `Localizable.strings`.
- parameter bundle: The receiver’s bundle to search. If bundle is `nil`,
the method attempts to use main bundle.
- returns: The formatted localized string with arguments.
*/
func localizedFormat(arguments: CVarArg..., using tableName: String?, in bundle: Bundle?) -> String {
return String(format: localized(using: tableName, in: bundle), arguments: arguments)
}
/**
Swift 2 friendly plural localization syntax with a format argument.
- parameter argument: Argument to determine pluralisation.
- parameter tableName: The receiver’s string table to search. If tableName is `nil`
or is an empty string, the method attempts to use `Localizable.strings`.
- parameter bundle: The receiver’s bundle to search. If bundle is `nil`,
the method attempts to use main bundle.
- returns: Pluralized localized string.
*/
func localizedPlural(argument: CVarArg, using tableName: String?, in bundle: Bundle?) -> String {
return NSString.localizedStringWithFormat(localized(using: tableName, in: bundle) as NSString, argument) as String
}
}
//
// IBDesignable+Localize1.swift
// Localize-Swift
//
// Copyright © 2020 Roy Marmelstein. All rights reserved.
//
import Foundation
import UIKit
// MARK: - UILabel localize Key extention for language in story board
@IBDesignable public extension UILabel {
@IBInspectable var localizeKey: String? {
set {
// set new value from dictionary
DispatchQueue.main.async {
self.text = newValue?.localized()
}
}
get {
return self.text
}
}
}
// MARK: - UIButton localize Key extention for language in story board
@IBDesignable public extension UIButton {
@IBInspectable var localizeKey: String? {
set {
// set new value from dictionary
DispatchQueue.main.async {
self.setTitle(newValue?.localized(), for: .normal)
}
}
get {
return self.titleLabel?.text
}
}
}
// MARK: - UITextView localize Key extention for language in story board
@IBDesignable public extension UITextView {
@IBInspectable var localizeKey: String? {
set {
// set new value from dictionary
DispatchQueue.main.async {
self.text = newValue?.localized()
}
}
get {
return self.text
}
}
}
// MARK: - UITextField localize Key extention for language in story board
@IBDesignable public extension UITextField {
@IBInspectable var localizeKey: String? {
set {
// set new value from dictionary
DispatchQueue.main.async {
self.placeholder = newValue?.localized()
}
}
get {
return self.placeholder
}
}
}
// MARK: - UINavigationItem localize Key extention for language in story board
@IBDesignable public extension UINavigationItem {
@IBInspectable var localizeKey: String? {
set {
// set new value from dictionary
DispatchQueue.main.async {
self.title = newValue?.localized()
}
}
get {
return self.title
}
}
}
PODS:
- BezierKit (0.10.0)
- Localize-Swift (3.2.0):
- Localize-Swift/LocalizeSwiftCore (= 3.2.0)
- Localize-Swift/UIKit (= 3.2.0)
- Localize-Swift/LocalizeSwiftCore (3.2.0)
- Localize-Swift/UIKit (3.2.0):
- Localize-Swift/LocalizeSwiftCore
- SnapKit (5.0.1)
DEPENDENCIES:
- BezierKit
- Localize-Swift
- SnapKit
SPEC REPOS:
trunk:
- BezierKit
- Localize-Swift
- SnapKit
SPEC CHECKSUMS:
BezierKit: 1828aca1675d68f0659c5353bc5b0d3399a3910c
Localize-Swift: 6f4475136bdb0d7b2882ea3d4ea919d70142b232
SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
PODFILE CHECKSUM: 9a72121885af918e1ac484fb10d514827acb4fbc
PODFILE CHECKSUM: 06893793de13524c28b9afdc6dca649b77af3601
COCOAPODS: 1.10.1
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......
......@@ -11,20 +11,27 @@
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Pods-1Weather.xcscheme</key>
<key>Localize-Swift.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>SnapKit.xcscheme</key>
<key>Pods-1Weather.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>SnapKit.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>3</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict/>
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_Localize_Swift : NSObject
@end
@implementation PodsDummy_Localize_Swift
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "Localize_Swift.h"
FOUNDATION_EXPORT double Localize_SwiftVersionNumber;
FOUNDATION_EXPORT const unsigned char Localize_SwiftVersionString[];
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Localize-Swift
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
SWIFT_VERSION = 5.3
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
framework module Localize_Swift {
umbrella header "Localize-Swift-umbrella.h"
export *
module * { export * }
}
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Localize-Swift
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
SWIFT_VERSION = 5.3
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
......@@ -25,6 +25,29 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## Localize-Swift
Copyright (c) 2015 Roy Marmelstein (http://roysapps.com/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## SnapKit
Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
......
......@@ -44,6 +44,35 @@ SOFTWARE.</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2015 Roy Marmelstein (http://roysapps.com/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>Localize-Swift</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
Permission is hereby granted, free of charge, to any person obtaining a copy
......
${PODS_ROOT}/Target Support Files/Pods-1Weather/Pods-1Weather-frameworks.sh
${BUILT_PRODUCTS_DIR}/BezierKit/BezierKit.framework
${BUILT_PRODUCTS_DIR}/Localize-Swift/Localize_Swift.framework
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
\ No newline at end of file
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BezierKit.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Localize_Swift.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
\ No newline at end of file
${PODS_ROOT}/Target Support Files/Pods-1Weather/Pods-1Weather-frameworks.sh
${BUILT_PRODUCTS_DIR}/BezierKit/BezierKit.framework
${BUILT_PRODUCTS_DIR}/Localize-Swift/Localize_Swift.framework
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
\ No newline at end of file
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BezierKit.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Localize_Swift.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
\ No newline at end of file
......@@ -176,10 +176,12 @@ code_sign_if_enabled() {
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/BezierKit/BezierKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Localize-Swift/Localize_Swift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/BezierKit/BezierKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Localize-Swift/Localize_Swift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
fi
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
......
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BezierKit" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BezierKit" "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BezierKit/BezierKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BezierKit/BezierKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift/Localize_Swift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "BezierKit" -framework "CoreGraphics" -framework "SnapKit" -framework "UIKit"
OTHER_LDFLAGS = $(inherited) -framework "BezierKit" -framework "CoreGraphics" -framework "Localize_Swift" -framework "SnapKit" -framework "UIKit"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
......
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BezierKit" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BezierKit" "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BezierKit/BezierKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BezierKit/BezierKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift/Localize_Swift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "BezierKit" -framework "CoreGraphics" -framework "SnapKit" -framework "UIKit"
OTHER_LDFLAGS = $(inherited) -framework "BezierKit" -framework "CoreGraphics" -framework "Localize_Swift" -framework "SnapKit" -framework "UIKit"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment