on master help sign in
Melk.xcodeproj/project.pbxproj+14−2
since splitsnapshots
unifiedsplit
@@ -10,6 +10,7 @@
10 0919AEA71F0D3D9D00894FFA /* SecondaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */; };
10 0919AEA71F0D3D9D00894FFA /* SecondaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */; };
11 0919AEA91F0D459600894FFA /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA81F0D459600894FFA /* CardView.swift */; };
11 0919AEA91F0D459600894FFA /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA81F0D459600894FFA /* CardView.swift */; };
12 0919AEAD1F0D706700894FFA /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEAC1F0D706700894FFA /* StackView.swift */; };
12 0919AEAD1F0D706700894FFA /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEAC1F0D706700894FFA /* StackView.swift */; };
13+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09231E341F2394ED007C836D /* CardContainer.swift */; };
13 09939A561F097DA90014719D /* TableViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09939A551F097DA90014719D /* TableViewCells.swift */; };
14 09939A561F097DA90014719D /* TableViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09939A551F097DA90014719D /* TableViewCells.swift */; };
14 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A456351F0ED9A800BAC32D /* CollectionView.swift */; };
15 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A456351F0ED9A800BAC32D /* CollectionView.swift */; };
15 09AFAB041F159C9200EBD200 /* StreamingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AFAB031F159C9200EBD200 /* StreamingService.swift */; };
16 09AFAB041F159C9200EBD200 /* StreamingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AFAB031F159C9200EBD200 /* StreamingService.swift */; };
@@ -69,6 +70,7 @@
69 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryViewController.swift; sourceTree = "<group>"; };
70 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryViewController.swift; sourceTree = "<group>"; };
70 0919AEA81F0D459600894FFA /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
71 0919AEA81F0D459600894FFA /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
71 0919AEAC1F0D706700894FFA /* StackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = "<group>"; };
72 0919AEAC1F0D706700894FFA /* StackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = "<group>"; };
73+ 09231E341F2394ED007C836D /* CardContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardContainer.swift; sourceTree = "<group>"; };
72 09939A551F097DA90014719D /* TableViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCells.swift; sourceTree = "<group>"; };
74 09939A551F097DA90014719D /* TableViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCells.swift; sourceTree = "<group>"; };
73 09A456351F0ED9A800BAC32D /* CollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = "<group>"; };
75 09A456351F0ED9A800BAC32D /* CollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = "<group>"; };
74 09AFAAFF1F159AE300EBD200 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/iOS/SwiftyJSON.framework; sourceTree = SOURCE_ROOT; };
76 09AFAAFF1F159AE300EBD200 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/iOS/SwiftyJSON.framework; sourceTree = SOURCE_ROOT; };
@@ -117,11 +119,21 @@
117/* End PBXFrameworksBuildPhase section */
119/* End PBXFrameworksBuildPhase section */
118
120
119/* Begin PBXGroup section */
121/* Begin PBXGroup section */
122+ 09231E331F2394C5007C836D /* Card */ = {
123+ isa = PBXGroup;
124+ children = (
125+ 0919AEA81F0D459600894FFA /* CardView.swift */,
126+ 09231E341F2394ED007C836D /* CardContainer.swift */,
127+ );
128+ name = Card;
129+ sourceTree = "<group>";
130+ };
120 098397F71F1E887F008C88DD /* Main */ = {
131 098397F71F1E887F008C88DD /* Main */ = {
121 isa = PBXGroup;
132 isa = PBXGroup;
122 children = (
133 children = (
123 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */,
134 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */,
124 09BF42551F08413000AFAD57 /* MainViewController.swift */,
135 09BF42551F08413000AFAD57 /* MainViewController.swift */,
136+ 09939A551F097DA90014719D /* TableViewCells.swift */,
125 );
137 );
126 name = Main;
138 name = Main;
127 sourceTree = "<group>";
139 sourceTree = "<group>";
@@ -176,12 +188,11 @@
176 098397F71F1E887F008C88DD /* Main */,
188 098397F71F1E887F008C88DD /* Main */,
177 098397F81F1E888F008C88DD /* Secondary */,
189 098397F81F1E888F008C88DD /* Secondary */,
178 098397F91F1E88A3008C88DD /* Streaming */,
190 098397F91F1E88A3008C88DD /* Streaming */,
191+ 09231E331F2394C5007C836D /* Card */,
179 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */,
192 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */,
180 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */,
193 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */,
181 09BF42121F08341B00AFAD57 /* PublicCrap.swift */,
194 09BF42121F08341B00AFAD57 /* PublicCrap.swift */,
182 09F03F351F1674ED000CE916 /* VKServer.swift */,
195 09F03F351F1674ED000CE916 /* VKServer.swift */,
183− 0919AEA81F0D459600894FFA /* CardView.swift */,
184− 09939A551F097DA90014719D /* TableViewCells.swift */,
185 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */,
196 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */,
186 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */,
197 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */,
187 09BF42171F08352E00AFAD57 /* VKData.swift */,
198 09BF42171F08352E00AFAD57 /* VKData.swift */,
@@ -294,6 +305,7 @@
294 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */,
305 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */,
295 09BF42101F0833C100AFAD57 /* MainCoordinator.swift in Sources */,
306 09BF42101F0833C100AFAD57 /* MainCoordinator.swift in Sources */,
296 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */,
307 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */,
308+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */,
297 09BF42131F08341B00AFAD57 /* MyWeirdness.swift in Sources */,
309 09BF42131F08341B00AFAD57 /* MyWeirdness.swift in Sources */,
298 0919AEA91F0D459600894FFA /* CardView.swift in Sources */,
310 0919AEA91F0D459600894FFA /* CardView.swift in Sources */,
299 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */,
311 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */,
Melk.xcodeproj/xcuserdata/ilya.xcuserdatad/xcschemes/Melk.xcscheme+3−3
@@ -42,9 +42,9 @@
42 </AdditionalOptions>
42 </AdditionalOptions>
43 </TestAction>
43 </TestAction>
44 <LaunchAction
44 <LaunchAction
45− buildConfiguration = "Debug"
45+ buildConfiguration = "Release"
46− selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
46+ selectedDebuggerIdentifier = ""
47− selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
47+ selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
48 launchStyle = "0"
48 launchStyle = "0"
49 useCustomWorkingDirectory = "NO"
49 useCustomWorkingDirectory = "NO"
50 ignoresPersistentStateOnLaunch = "NO"
50 ignoresPersistentStateOnLaunch = "NO"
Melk/CardContainer.swiftnew file+100
@@ -0,0 +1,100 @@
1+//
2+// CardContainer.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/22/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class CardContainer: UIView {
12+
13+ func animate() {
14+ blurView.effect = blur
15+ card.transform = smallTransform
16+ card.layer.opacity = 0
17+ shadowView.layer.opacity = 0
18+ shadowView.transform = smallTransform
19+ shadowView.layer.shadowRadius = 0
20+ UIView.animate(withDuration: 0.5, delay: 0, options: [.curveEaseInOut], animations: {
21+ self.card.transform = self.normalTransform
22+ self.shadowView.transform = self.normalTransform
23+ self.shadowView.layer.shadowRadius = self.shadowRadius
24+ self.shadowView.layer.opacity = 1
25+ self.blurView.effect = nil
26+ self.card.layer.opacity = 1
27+ }, completion: nil)
28+ }
29+
30+ // MARK: Passthrough to CardView
31+ var post: WallPost? {
32+ get {
33+ return card.post
34+ }
35+ set {
36+ card.post = newValue
37+ }
38+ }
39+
40+ var minWidth: CGFloat {
41+ get {
42+ return card.minWidth
43+ }
44+ set {
45+ card.minWidth = newValue
46+ }
47+ }
48+
49+
50+ var height: CGFloat {
51+ get {
52+ return card.height
53+ }
54+ set {
55+ card.height = newValue
56+ }
57+ }
58+
59+ private let card: CardView
60+ private let blurView = UIVisualEffectView()
61+ private let shadowView = UIView()
62+ private let blur = UIBlurEffect(style: UIBlurEffectStyle.light)
63+ private let scale: CGFloat = 0.5
64+ private let smallTransform: CGAffineTransform
65+ private let normalTransform = CGAffineTransform(scaleX: 1, y: 1)
66+ private let shadowRadius: CGFloat = 12
67+ private let shadowOpacity: Float = 0.15
68+ private let shadowOffset: CGFloat = 16
69+
70+ override init(frame: CGRect) {
71+ card = CardView(frame: frame)
72+ smallTransform = CGAffineTransform(scaleX: scale, y: scale)
73+ super.init(frame: frame)
74+ shadowView.layer.shadowColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1).cgColor
75+ shadowView.layer.shadowRadius = shadowRadius
76+ shadowView.layer.shadowOpacity = shadowOpacity
77+ shadowView.backgroundColor = UIColor.clear
78+ shadowView.clipsToBounds = false
79+ shadowView.layer.shadowOffset = CGSize.zero
80+ addSubview(shadowView)
81+ addSubview(card)
82+ addSubview(blurView)
83+ }
84+
85+ required init?(coder aDecoder: NSCoder) {
86+ fatalError("init(coder:) has not been implemented")
87+ }
88+
89+ override func layoutSubviews() {
90+ super.layoutSubviews()
91+ card.layoutIfNeeded()
92+ self.frame = card.frame
93+ shadowView.layer.shadowPath = CGPath(roundedRect: card.frame, cornerWidth: card.layer.cornerRadius, cornerHeight: card.layer.cornerRadius,
94+ transform: nil)
95+ shadowView.layer.shadowOffset.height = shadowOffset
96+ shadowView.frame = card.frame
97+ blurView.frame = CGRect(x: -shadowRadius, y: 0, width: card.frame.width + shadowRadius*2, height: card.frame.height + shadowRadius + shadowOffset)
98+ }
99+
100+}
Melk/CardView.swift+126−11
@@ -12,21 +12,51 @@ class CardView: UIView {
12
12
13 var post: WallPost? {
13 var post: WallPost? {
14 didSet {
14 didSet {
15− print(post?.owner)
15+// print(post?.owner)
16 update()
16 update()
17 }
17 }
18 }
18 }
19+ var minWidth: CGFloat = 300 {
20+ didSet {
21+// update()
22+ }
23+ }
24+ var height: CGFloat = 400 {
25+ didSet {
26+// update()
27+ }
28+ }
19
29
20 override init(frame: CGRect) {
30 override init(frame: CGRect) {
21 super.init(frame: frame)
31 super.init(frame: frame)
22 layer.masksToBounds = true
32 layer.masksToBounds = true
23 layer.cornerRadius = 20
33 layer.cornerRadius = 20
34+ gradient.colors = [#colorLiteral(red: 0.9841352105, green: 0.9841352105, blue: 0.9841352105, alpha: 1).cgColor, #colorLiteral(red: 0.9456828237, green: 0.9456828237, blue: 0.9456828237, alpha: 1).cgColor]
35+ gradient.startPoint = CGPoint(x: 0.5, y: 0)
36+ gradient.endPoint = CGPoint(x: 0.5, y: 1)
37+ layer.addSublayer(gradient)
24− backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
38+// backgroundColor = #colorLiteral(red: 0.9638966278, green: 0.9734401588, blue: 0.9734401588, alpha: 1)
25 }
39 }
26
40
27 required init?(coder aDecoder: NSCoder) {
41 required init?(coder aDecoder: NSCoder) {
28 fatalError("init(coder:) has not been implemented")
42 fatalError("init(coder:) has not been implemented")
29 }
43 }
44+
45+ private let gradient = CAGradientLayer()
46+
47+
48+ private let padding = CGPoint(x: 32, y: 32)
49+ private let vPadding1: CGFloat = 16
50+ private let vPadding2: CGFloat = 16
51+ private var minTextHeight: CGFloat {
52+ return CGFloat(height * 0.2)
53+ }
54+ private let gradientRatio: CGFloat = 0.2
55+ private let minGradientAmount: CGFloat = 70
56+ private let hiddenByGradient: CGFloat = 10
57+ private var maxAttachmentDim: CGFloat {
58+ return minWidth / 2.3
59+ }
30
60
31// private let avatar = UIImage()
61// private let avatar = UIImage()
32// private let name = UIImage()
62// private let name = UIImage()
@@ -35,14 +65,13 @@ class CardView: UIView {
35 subviews.map {$0.removeFromSuperview()}
65 subviews.map {$0.removeFromSuperview()}
36
66
37 let largeStack = StackView(axis: .vertical, alignment: .left)
67 let largeStack = StackView(axis: .vertical, alignment: .left)
38− largeStack.padding = CGPoint(x: 32, y: 32)
68+ largeStack.padding = padding
39 addSubview(largeStack)
69 addSubview(largeStack)
40
70
41 let userView = StackView(axis: .horizontal, alignment: .center)
71 let userView = StackView(axis: .horizontal, alignment: .center)
42−// userView.backgroundColor = #colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)
43
72
44
73
45− let avatarDim: CGFloat = 50
74+ let avatarDim: CGFloat = 64
46 let avatar = UIImageView()
75 let avatar = UIImageView()
47 post?.owner?.avatar?.load(into: avatar)
76 post?.owner?.avatar?.load(into: avatar)
48 avatar.layer.cornerRadius = avatarDim/2
77 avatar.layer.cornerRadius = avatarDim/2
@@ -52,30 +81,116 @@ class CardView: UIView {
52 avatar.frame.size = CGSize(width: avatarDim, height: avatarDim)
81 avatar.frame.size = CGSize(width: avatarDim, height: avatarDim)
53 userView.addArrangedSubview(.view(avatar))
82 userView.addArrangedSubview(.view(avatar))
54
83
55− userView.addArrangedSubview(.spacer(8))
84+ userView.addArrangedSubview(.spacer(16))
56
85
57 let userName = UILabel()
86 let userName = UILabel()
58 let firstName = post?.owner?.firstName ?? ""
87 let firstName = post?.owner?.firstName ?? ""
59 let lastName = post?.owner?.lastName ?? ""
88 let lastName = post?.owner?.lastName ?? ""
60 userName.text = "\(firstName) \(lastName)"
89 userName.text = "\(firstName) \(lastName)"
61 userName.font = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy)
90 userName.font = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy)
91+ userName.textColor = UIColor.darkText
62 userName.sizeToFit()
92 userName.sizeToFit()
63 userView.addArrangedSubview(.view(userName))
93 userView.addArrangedSubview(.view(userName))
64− userView.frame.origin = CGPoint(x: 16, y: 16)
94+
95+ userView.layoutIfNeeded()
65
96
66 largeStack.addArrangedSubview(.view(userView))
97 largeStack.addArrangedSubview(.view(userView))
67
98
68− largeStack.addArrangedSubview(.spacer(16))
99+ largeStack.addArrangedSubview(.spacer(vPadding1))
100+
101+
102+ let width = max(minWidth - 2 * padding.x, userView.frame.width)
103+
104+
105+ let photoView = CollectionView()
106+ photoView.alignment = .top
107+ photoView.horizontalPadding = 8
108+ photoView.verticalPadding = 8
109+ photoView.padding = CGPoint.zero
110+ photoView.maxWidth = width
111+ var maxPhotoDim: CGFloat = maxAttachmentDim
112+ if let attachments = post?.attachments {
113+ for attachment in attachments {
114+ if let v = attachment.value {
115+ switch v {
116+ case .photo(let photo):
117+ if var pWidth = photo.width.value >>> {CGFloat($0)},
118+ var pHeight = photo.height.value >>> {CGFloat($0)} {
119+ let photoV = UIImageView()
120+ photoV.layer.minificationFilter = kCAFilterTrilinear
121+ photoV.contentMode = .scaleAspectFill
122+ photo.image?.load(into: photoV)
123+ var k: CGFloat!
124+ if pWidth > pHeight {
125+ k = CGFloat(pWidth) / maxPhotoDim
126+ } else {
127+ k = CGFloat(pHeight) / maxPhotoDim
128+ }
129+ pWidth /= k
130+ pHeight /= k
131+ photoV.frame.size = CGSize(width: pWidth, height: pHeight)
132+ photoView.addArrangedSubview(photoV)
133+ }
134+
135+ default:
136+ break // TODO: Handle other data types
137+ }
138+ }
139+ }
140+ }
141+ photoView.layoutIfNeeded()
142+
143+ var textHeight = height
144+ textHeight -= padding.y * 2 + vPadding1 + userView.frame.height
145+ if let count = post?.attachments.count{
146+ if count > 0 {
147+ textHeight -= vPadding2 + photoView.frame.height
148+ }
149+ }
150+ print(textHeight)
151+ textHeight = max(textHeight, minTextHeight)
152+ print(textHeight)
69
153
70 let body = UILabel()
154 let body = UILabel()
71 body.numberOfLines = 0
155 body.numberOfLines = 0
72− let bodyWidth: Double = 300
156+ let bodyWidth: Double = Double(width)
73 body.font = UIFont.systemFont(ofSize: 22, weight: UIFontWeightSemibold)
157 body.font = UIFont.systemFont(ofSize: 22, weight: UIFontWeightSemibold)
74− body.text = post?.body ?? "Spicy jalapeno flank landjaeger sausage tongue tail fatback frankfurter. Meatball tongue tail short ribs pastrami. Short ribs chicken pig tenderloin sausage pork. Corned beef t-bone pancetta jerky chuck picanha venison, turducken tenderloin shoulder boudin drumstick hamburger. Pork meatball meatloaf prosciutto pig andouille. Sirloin bacon hamburger short ribs pork cupim shank biltong turducken bresaola kevin prosciutto shoulder meatball. Strip steak frankfurter pork chop biltong jowl beef rump venison pancetta turkey tongue."
158+ body.text = post?.body
159+ body.textColor = UIColor.darkText
75 let size = body.textRect(forBounds: CGRect(x: 0, y: 0, width: bodyWidth, height: Double.infinity), limitedToNumberOfLines: 0).size
160 let size = body.textRect(forBounds: CGRect(x: 0, y: 0, width: bodyWidth, height: Double.infinity), limitedToNumberOfLines: 0).size
76− body.frame.size = size
161+ body.frame.size.width = size.width
162+ if size.height > textHeight {
163+ body.frame.size.height = textHeight
164+ let maskView = UIView()
165+ let maskLayer = CAGradientLayer()
166+ maskLayer.colors = [UIColor.black.cgColor, UIColor.clear.cgColor]
167+ maskLayer.endPoint = CGPoint(x: 0.5, y: 1 - (hiddenByGradient/textHeight))
168+ let minPoint = minGradientAmount / textHeight
169+ maskLayer.startPoint = CGPoint(x: 0.5, y: 1 - max(minPoint, gradientRatio))
170+ print(maskLayer.startPoint)
171+ maskLayer.frame = body.bounds
172+ maskView.frame = body.bounds
173+ maskView.layer.addSublayer(maskLayer)
174+ body.mask = maskView
175+ } else {
176+ body.frame.size.height = size.height
177+ }
77
178
78 largeStack.addArrangedSubview(.view(body))
179 largeStack.addArrangedSubview(.view(body))
79
180
181+ if let count = post?.attachments.count{
182+ if count > 0 {
183+ largeStack.addArrangedSubview(.spacer(vPadding2))
184+ largeStack.addArrangedSubview(.view(photoView))
185+ }
186+ }
187+
188+ largeStack.layoutIfNeeded()
189+
190+ frame.size = CGSize(width: width + padding.x * 2, height: min(height, largeStack.frame.height))
191+
192+ gradient.frame = self.bounds
193+ gradient.endPoint = CGPoint(x: 0.5, y: bounds.height / height)
194+
80 }
195 }
81}
196}
Melk/CollectionView.swift+4−2
@@ -68,7 +68,7 @@ class CollectionView: UIView {
68 var firstStack = true
68 var firstStack = true
69
69
70 for view in arrangedSubviews {
70 for view in arrangedSubviews {
71− print("new view")
71+// print("new view")
72 if firstView {
72 if firstView {
73 lastStack.addArrangedSubview(.view(view))
73 lastStack.addArrangedSubview(.view(view))
74 firstView = false
74 firstView = false
@@ -94,7 +94,9 @@ class CollectionView: UIView {
94 }
94 }
95 }
95 }
96 lastStack.layoutIfNeeded()
96 lastStack.layoutIfNeeded()
97+ if !firstStack {
97− largeStack.addArrangedSubview(.spacer(verticalPadding))
98+ largeStack.addArrangedSubview(.spacer(verticalPadding))
99+ }
98 largeStack.addArrangedSubview(.view(lastStack))
100 largeStack.addArrangedSubview(.view(lastStack))
99 largeStack.frame.origin = CGPoint.zero
101 largeStack.frame.origin = CGPoint.zero
100 largeStack.layoutIfNeeded()
102 largeStack.layoutIfNeeded()
Melk/MainCoordinator.swift+8−2
@@ -27,6 +27,14 @@ class MainCoordinator {
27 deleteRealmIfMigrationNeeded: true)
27 deleteRealmIfMigrationNeeded: true)
28
28
29 navC = UINavigationController(rootViewController: mainVC)
29 navC = UINavigationController(rootViewController: mainVC)
30+
31+ mainVC.present = { tags in
32+ if tags.count > 0 {
33+ self.secondaryCoord.present(tag: tags.first!)
34+ self.stream.rules = tags
35+ self.stream.connect()
36+ }
37+ }
30 updateScreens()
38 updateScreens()
31 stream.handleResult = { result in
39 stream.handleResult = { result in
32 self.secondaryCoord.add(result: result)
40 self.secondaryCoord.add(result: result)
@@ -44,8 +52,6 @@ class MainCoordinator {
44// }
52// }
45 print("didAuthorize")
53 print("didAuthorize")
46 window.rootViewController = navC
54 window.rootViewController = navC
47− stream.rules = ["я", "а"]
48− stream.connect()
49 }
55 }
50
56
51 func updateScreens() {
57 func updateScreens() {
Melk/MainViewController.swift+22−2
@@ -29,7 +29,9 @@ class MainViewController: UIViewController {
29 }
29 }
30
30
31 @objc private func presentTags() {
31 @objc private func presentTags() {
32−
32+ present?(
33+ mainView.tags.flatMap({$0}).filter({$0 != ""})
34+ )
33 }
35 }
34}
36}
35
37
@@ -45,6 +47,8 @@ class MainView: UIView {
45 tableView.rowHeight = UITableViewAutomaticDimension
47 tableView.rowHeight = UITableViewAutomaticDimension
46 tableView.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
48 tableView.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
47 tableView.register(TagTableViewCell.self, forCellReuseIdentifier: TagTableViewCell.identifier)
49 tableView.register(TagTableViewCell.self, forCellReuseIdentifier: TagTableViewCell.identifier)
50+ tableView.separatorStyle = .none
51+ tableView.allowsSelection = false
48 setNeedsLayout()
52 setNeedsLayout()
49 }
53 }
50
54
@@ -52,6 +56,17 @@ class MainView: UIView {
52 tableView.frame = bounds
56 tableView.frame = bounds
53 }
57 }
54
58
59+ fileprivate var tags: [String?] = [nil] {
60+ didSet {
61+ if tags.last! != nil && tags.last! != "" {
62+ if tags.count < 10 {
63+ tags.append(nil)
64+ tableView.insertRows(at: [IndexPath.init(row: tags.count - 1, section: 0)], with: .automatic)
65+ }
66+ }
67+ }
68+ }
69+
55 required init?(coder aDecoder: NSCoder) {
70 required init?(coder aDecoder: NSCoder) {
56 fatalError("init(coder:) has not been implemented")
71 fatalError("init(coder:) has not been implemented")
57 }
72 }
@@ -62,13 +77,18 @@ extension MainView: UITableViewDataSource {
62 if let cell = tableView.dequeueReusableCell(withIdentifier: TagTableViewCell.identifier) as? TagTableViewCell {
77 if let cell = tableView.dequeueReusableCell(withIdentifier: TagTableViewCell.identifier) as? TagTableViewCell {
63// cell.titleText = "Testing testing 1 2 3"
78// cell.titleText = "Testing testing 1 2 3"
64 cell.style = indexPath.item == 0 ? .large : .normal
79 cell.style = indexPath.item == 0 ? .large : .normal
80+ let index = indexPath.row
81+ cell.tagDidChange = { tag in
82+ self.tags[index] = tag
83+ }
84+ cell.tagString = tags[index]
65 return cell
85 return cell
66 }
86 }
67 return UITableViewCell()
87 return UITableViewCell()
68 }
88 }
69
89
70 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
90 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
71− return 3
91+ return tags.count
72 }
92 }
73
93
74 func numberOfSections(in tableView: UITableView) -> Int {
94 func numberOfSections(in tableView: UITableView) -> Int {
Melk/SecondaryCoordinator.swift+12
@@ -12,12 +12,21 @@ class SecondaryCoordinator {
12
12
13 var windows: [UIWindow] = []
13 var windows: [UIWindow] = []
14
14
15+ private var didStartPresenting = false
16+ private var tag: String = ""
17+
15 func add(result: StreamingServiceResult) {
18 func add(result: StreamingServiceResult) {
16 for vc in VCs {
19 for vc in VCs {
17 vc.add(result: result)
20 vc.add(result: result)
18 }
21 }
19 }
22 }
20
23
24+ func present(tag newTag: String) {
25+ didStartPresenting = true
26+ self.tag = newTag
27+ VCs.map({ $0.present(tag: newTag) })
28+ }
29+
21 init() {
30 init() {
22
31
23 }
32 }
@@ -36,6 +45,9 @@ class SecondaryCoordinator {
36 VCs.append(VC)
45 VCs.append(VC)
37 window.rootViewController = VC
46 window.rootViewController = VC
38 windows.append(window)
47 windows.append(window)
48+ if didStartPresenting {
49+ VC.present(tag: tag)
50+ }
39 }
51 }
40 }
52 }
41}
53}
Melk/SecondaryViewController.swift+111−2
@@ -19,6 +19,10 @@ class SecondaryViewController: UIViewController {
19 sView.add(result: result)
19 sView.add(result: result)
20 }
20 }
21
21
22+ func present(tag: String) {
23+ sView.present(tag: tag)
24+ }
25+
22 required init?(coder aDecoder: NSCoder) {
26 required init?(coder aDecoder: NSCoder) {
23 fatalError("init(coder:) has not been implemented")
27 fatalError("init(coder:) has not been implemented")
24 }
28 }
@@ -27,18 +31,123 @@ class SecondaryViewController: UIViewController {
27}
31}
28
32
29class SecondaryView: UIView {
33class SecondaryView: UIView {
34+
35+ var mainTag: String? {
36+ get {
37+ return tagLabel.text
38+ }
39+ set {
40+ tagLabel.text = newValue
41+
42+ }
43+ }
44+
45+
46+ private let spacer: CGFloat = 32
47+ private let bottomSpacer: CGFloat = 64
48+ private let tagSpacer: CGFloat = 32
49+ private let tagLabel = UILabel()
50+ private let melkLabel = UILabel()
51+
52+ func present(tag: String) {
53+ UIView.perform(.delete, on: [melkLabel], options: [], animations: nil) { (_) in
54+ self.melkLabel.removeFromSuperview()
55+ self.mainTag = tag
56+ self.tagLabel.sizeToFit()
57+ self.layoutIfNeeded(animated: true)
58+ }
59+ }
60+
30 func add(result: StreamingServiceResult) {
61 func add(result: StreamingServiceResult) {
31 switch result {
62 switch result {
32 case .post(let post):
63 case .post(let post):
64+ let card = CardContainer()
65+ card.minWidth = min(bounds.width / 2.3, 550)
66+ card.height = cardHeight
33 card.post = post
67 card.post = post
68+ cards.addArrangedSubview(.spacer(spacer))
69+ cards.addArrangedSubview(.view(card))
70+ card.animate()
34 }
71 }
72+ self.layoutIfNeeded(animated: true)
73+// self.setNeedsLayout(animated: true) // Doesn't work for some reason
35 }
74 }
36
75
37− let card = CardView(frame: CGRect(x: 20, y: 20, width: 600, height: 300))
76+// private let card = CardView()
77+ private let cards = StackView(axis: .horizontal, alignment: .top)
38
78
39 override init(frame: CGRect) {
79 override init(frame: CGRect) {
40 super.init(frame: frame)
80 super.init(frame: frame)
81+ backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
41− addSubview(card)
82+ addSubview(cards)
83+
84+ melkLabel.text = "Melk"
85+ melkLabel.font = UIFont.systemFont(ofSize: 200, weight: UIFontWeightHeavy)
86+ melkLabel.textColor = #colorLiteral(red: 0.9601849914, green: 0.9601849914, blue: 0.9601849914, alpha: 1)
87+ addSubview(melkLabel)
88+
89+ tagLabel.font = UIFont.systemFont(ofSize: 69, weight: UIFontWeightBold)
90+ tagLabel.textColor = #colorLiteral(red: 0.3176470588, green: 0.3179988265, blue: 0.3179988265, alpha: 1)
91+ addSubview(tagLabel)
92+ }
93+
94+ func setNeedsLayout(animated: Bool) {
95+ self.animate = animated
96+ self.setNeedsLayout()
97+ }
98+ func layoutIfNeeded(animated: Bool) {
99+ self.animate = animated
100+ self.layoutIfNeeded()
101+ }
102+ private var animate: Bool = false
103+
104+ private func cleanCards() {
105+ var offset = 0
106+ outLoop: for (i, element) in cards.arrangedSubviews.enumerated() {
107+ switch element {
108+ case .view(let card):
109+ let cardFrame = convert(card.frame, from: cards)
110+ if cardFrame.intersects(bounds) {
111+ break outLoop
112+ } else {
113+ cards.arrangedSubviews.remove(at: i-1-offset)
114+ cards.arrangedSubviews.remove(at: i-1-offset)
115+ offset += 2
116+ }
117+ case .spacer(_):
118+ break
119+ }
120+ }
121+ }
122+
123+ private let padding = CGPoint(x: 32, y: 16)
124+ private var cardHeight: CGFloat = 400
125+
126+ override func layoutSubviews() {
127+ super.layoutSubviews()
128+
129+ melkLabel.sizeToFit()
130+ melkLabel.center = center
131+
132+ let updates = {
133+ self.tagLabel.sizeToFit()
134+ self.tagLabel.center.x = self.center.x
135+ self.tagLabel.frame.origin.y = self.tagSpacer
136+ self.cards.frame.origin.y = self.tagLabel.frame.maxY + self.padding.y
137+ self.cards.layoutIfNeeded()
138+ self.cards.frame.origin.x = self.frame.width - self.padding.x - self.cards.frame.width
139+ }
140+ if animate {
141+ UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.6, options: [], animations: updates, completion: { _ in
142+ self.cleanCards()
143+ })
144+// UIView.animate(withDuration: 0.4, delay: 0.0, options: [.curveEaseOut], animations: updates, completion: nil)
145+ } else {
146+ updates()
147+ self.cleanCards()
148+ }
149+ cardHeight = bounds.height - tagLabel.frame.height - padding.y - tagSpacer - bottomSpacer
150+ animate = false
42 }
151 }
43
152
44 required init?(coder aDecoder: NSCoder) {
153 required init?(coder aDecoder: NSCoder) {
Melk/StackView.swift+3−1
@@ -44,6 +44,8 @@ class StackView: UIView {
44 super.layoutSubviews()
44 super.layoutSubviews()
45 print("laying out subviews")
45 print("laying out subviews")
46
46
47+ subviews.map({$0.removeFromSuperview()}) // TODO: There must be a better way!
48+
47 var height: CGFloat = 0
49 var height: CGFloat = 0
48 var width: CGFloat = 0
50 var width: CGFloat = 0
49 switch axis {
51 switch axis {
@@ -62,7 +64,7 @@ class StackView: UIView {
62 height += a
64 height += a
63 }
65 }
64 case .view(let view):
66 case .view(let view):
65−
67+ view.layoutIfNeeded()
66 switch axis {
68 switch axis {
67 case .horizontal:
69 case .horizontal:
68 view.frame.origin.x = width
70 view.frame.origin.x = width
Melk/StreamingParser.swift+1−1
@@ -50,7 +50,7 @@ class StreamingParser {
50 let complete = {
50 let complete = {
51 comp -= 1
51 comp -= 1
52 if comp == 0 {
52 if comp == 0 {
53− print(post.owner)
53+// print(post.owner)
54 completion(.post(post))
54 completion(.post(post))
55 }
55 }
56 }
56 }
Melk/TableViewCells.swift+12−3
@@ -57,6 +57,12 @@ class TagTableViewCell: UITableViewCell {
57 }
57 }
58 }
58 }
59
59
60+ var tagDidChange: ((String?) -> ())?
61+
62+ @objc private func textDidChange() {
63+ tagDidChange?(tagString)
64+ }
65+
60 var style: TagStyle = .normal {
66 var style: TagStyle = .normal {
61 didSet {
67 didSet {
62 updateStyle()
68 updateStyle()
@@ -70,6 +76,9 @@ class TagTableViewCell: UITableViewCell {
70
76
71 textField.borderStyle = .roundedRect
77 textField.borderStyle = .roundedRect
72 textField.adjustsFontSizeToFitWidth = false
78 textField.adjustsFontSizeToFitWidth = false
79+ textField.autocapitalizationType = .none
80+ textField.autocorrectionType = .no
81+ textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged)
73
82
74 contentView.addSubview(textField)
83 contentView.addSubview(textField)
75// textField.delegate = self
84// textField.delegate = self
@@ -92,7 +101,7 @@ class TagTableViewCell: UITableViewCell {
92 CGRect.infinite, limitedToNumberOfLines: 1)
101 CGRect.infinite, limitedToNumberOfLines: 1)
93 .height + 2*2
102 .height + 2*2
94
103
95− let frame = CGRect(x: 16, y: 8, width: bounds.width - 16*2, height: height)
104+ let frame = CGRect(x: 16, y: style == .large ? 32 : 4, width: bounds.width - 16*2, height: height)
96 textField.frame = frame
105 textField.frame = frame
97 print(textField.frame)
106 print(textField.frame)
98 }
107 }
@@ -103,8 +112,8 @@ class TagTableViewCell: UITableViewCell {
103
112
104
113
105 static let identifier = "io.Melk.tagTableViewCell"
114 static let identifier = "io.Melk.tagTableViewCell"
106− static let largeHeight: CGFloat = 37.5 + 8*2
115+ static let largeHeight: CGFloat = 37.5 + 16 + 32
107− static let normalHeight: CGFloat = 28.0 + 8*2
116+ static let normalHeight: CGFloat = 28.0 + 4*2
108}
117}
109
118
110//extension TagTableViewCell: UITextFieldDelegate {
119//extension TagTableViewCell: UITextFieldDelegate {
Melk/VKData.swift+8−8
@@ -49,7 +49,7 @@ class VKData {
49 }
49 }
50 user.birthday = formatter.date(from: birthday)
50 user.birthday = formatter.date(from: birthday)
51 }
51 }
52− user.avatar = RealmPhoto.by(user.avatarID, surl: json["photo_max"].string)
52+ user.avatar = RealmPhoto.by(user.avatarID, surl: json["photo_100"].string)// was "photo_max"
53 user.photo = RealmPhoto.by(user.photoID, surl: json["photo_max_orig"].string)
53 user.photo = RealmPhoto.by(user.photoID, surl: json["photo_max_orig"].string)
54 if let friendStat = json["friend_status"].int {
54 if let friendStat = json["friend_status"].int {
55 user.friendStatus = FriendStatus(rawValue: friendStat)
55 user.friendStatus = FriendStatus(rawValue: friendStat)
@@ -289,13 +289,13 @@ class VKData {
289 photo.width.value = json["width"].int
289 photo.width.value = json["width"].int
290 photo.height.value = json["height"].int
290 photo.height.value = json["height"].int
291 var surl: String?
291 var surl: String?
292− if let imageSurl = json["photo_2560"].string {
292+// if let imageSurl = json["photo_2560"].string {
293− surl = imageSurl
293+// surl = imageSurl
294− } else if let imageSurl = json["photo_1280"].string {
294+// } else if let imageSurl = json["photo_1280"].string {
295− surl = imageSurl
295+// surl = imageSurl
296− } else if let imageSurl = json["photo_807"].string {
296+// } else if let imageSurl = json["photo_807"].string {
297− surl = imageSurl
297+// surl = imageSurl
298− } else if let imageSurl = json["photo_604"].string {
298+ /*} else*/ if let imageSurl = json["photo_604"].string {
299 surl = imageSurl
299 surl = imageSurl
300 } else if let imageSurl = json["photo_130"].string {
300 } else if let imageSurl = json["photo_130"].string {
301 surl = imageSurl
301 surl = imageSurl
Melk/VKServer.swift+2−2
@@ -81,7 +81,7 @@ class VKServer {
81 let id = user.id
81 let id = user.id
82 let params: Parameters = [
82 let params: Parameters = [
83 "user_ids": id,
83 "user_ids": id,
84− "fields": "photo_max"
84+ "fields": "photo_100"
85 ]
85 ]
86 Alamofire.request(url(for: "users.get"), parameters: params)
86 Alamofire.request(url(for: "users.get"), parameters: params)
87 .validate().responseData { (response) in
87 .validate().responseData { (response) in
@@ -118,6 +118,6 @@ class VKServer {
118}
118}
119
119
120fileprivate func url(`for` method: String) -> URL {
120fileprivate func url(`for` method: String) -> URL {
121− let surl = "https://api.vk.com/method/\(method)?access_token=\(accessKey)&v=\(vkAPIVersion)"
121+ let surl = "https://api.vk.com/method/\(method)?access_token=\(accessKey)&v=\(vkAPIVersion)&lang=0"
122 return URL.init(string: surl)!
122 return URL.init(string: surl)!
123}
123}
README.mdnew file+17
@@ -0,0 +1,17 @@
1+# Melk
2+
3+_Might not work properly with new streaming API. This project was developed while the API was in beta._
4+
5+[![Melk demo](http://img.youtube.com/vi/9SNc0bLDCL0/0.jpg)](http://www.youtube.com/watch?v=9SNc0bLDCL0)
6+
7+При проведении различных мероприятий организаторы довольно часто призывают людей, посещающих эти мероприятия, выложить у себя в соц сети что-нибудь об этом мероприятии, чаще всего под мероприятие даже выделяется отдельный хештег.
8+
9+Для привлечения внимания к хештегу и поощрения его использования можно выводить некоторые из выложенных с хештегом записей туда, где все смогут это увидеть.
10+
11+При проведении больших мероприятий желательно использовать большое количество независимых систем, чтобы если летит что-то одно, все остальное продолжало работать.
12+
13+Полноценный десктопный компьютер для данной цели не нужен, поэтому приложение работает просто на телефоне или планшете.
14+
15+Самый первый хештег или ключевое слово будет крупно показано вместе со всеми записями.
16+
17+После того, как будет нажата кнопка “Present”, записи с основным хештегом будут выводиться на любые подключенные к устройству экраны.