on master help sign in
15 files changed+526−74
since splitsnapshots
unifiedsplit
Melk.xcodeproj/project.pbxproj+45−9 Melk.xcodeproj/xcuserdata/ilya.xcuserdatad/xcschemes/Melk.xcscheme+3−3 Melk/CardContainer.swift+100 Melk/CardView.swift+126−10 Melk/CollectionView.swift+4−2 Melk/MainCoordinator.swift+9−2 Melk/MainViewController.swift+22−2 Melk/SecondaryCoordinator.swift+24−1 Melk/SecondaryViewController.swift+125−9 Melk/StackView.swift+3−1 Melk/StreamingParser.swift+24−21 Melk/TableViewCells.swift+12−3 Melk/VKData.swift+9−9 Melk/VKServer.swift+3−2 README.md+17
Melk.xcodeproj/project.pbxproj+45−9
@@ -10,6 +10,7 @@
1010 0919AEA71F0D3D9D00894FFA /* SecondaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */; };
1111 0919AEA91F0D459600894FFA /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA81F0D459600894FFA /* CardView.swift */; };
1212 0919AEAD1F0D706700894FFA /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEAC1F0D706700894FFA /* StackView.swift */; };
13+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09231E341F2394ED007C836D /* CardContainer.swift */; };
1314 09939A561F097DA90014719D /* TableViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09939A551F097DA90014719D /* TableViewCells.swift */; };
1415 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A456351F0ED9A800BAC32D /* CollectionView.swift */; };
1516 09AFAB041F159C9200EBD200 /* StreamingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AFAB031F159C9200EBD200 /* StreamingService.swift */; };
@@ -69,6 +70,7 @@
6970 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryViewController.swift; sourceTree = "<group>"; };
7071 0919AEA81F0D459600894FFA /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
7172 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>"; };
7274 09939A551F097DA90014719D /* TableViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCells.swift; sourceTree = "<group>"; };
7375 09A456351F0ED9A800BAC32D /* CollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = "<group>"; };
7476 09AFAAFF1F159AE300EBD200 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/iOS/SwiftyJSON.framework; sourceTree = SOURCE_ROOT; };
@@ -117,6 +119,44 @@
117119/* End PBXFrameworksBuildPhase section */
118120
119121/* 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+ };
131+ 098397F71F1E887F008C88DD /* Main */ = {
132+ isa = PBXGroup;
133+ children = (
134+ 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */,
135+ 09BF42551F08413000AFAD57 /* MainViewController.swift */,
136+ 09939A551F097DA90014719D /* TableViewCells.swift */,
137+ );
138+ name = Main;
139+ sourceTree = "<group>";
140+ };
141+ 098397F81F1E888F008C88DD /* Secondary */ = {
142+ isa = PBXGroup;
143+ children = (
144+ 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */,
145+ 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */,
146+ );
147+ name = Secondary;
148+ sourceTree = "<group>";
149+ };
150+ 098397F91F1E88A3008C88DD /* Streaming */ = {
151+ isa = PBXGroup;
152+ children = (
153+ 09AFAB031F159C9200EBD200 /* StreamingService.swift */,
154+ 09F03F221F164E7E000CE916 /* StreamingParser.swift */,
155+ 09F0D79D1F129DCB00A13751 /* Streaming.swift */,
156+ );
157+ name = Streaming;
158+ sourceTree = "<group>";
159+ };
120160 09A456341F0ED98800BAC32D /* Public */ = {
121161 isa = PBXGroup;
122162 children = (
@@ -145,19 +185,14 @@
145185 09BF41ED1F07F71F00AFAD57 /* Melk */ = {
146186 isa = PBXGroup;
147187 children = (
188+ 098397F71F1E887F008C88DD /* Main */,
189+ 098397F81F1E888F008C88DD /* Secondary */,
190+ 098397F91F1E88A3008C88DD /* Streaming */,
191+ 09231E331F2394C5007C836D /* Card */,
148192 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */,
149193 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */,
150194 09BF42121F08341B00AFAD57 /* PublicCrap.swift */,
151− 09F0D79D1F129DCB00A13751 /* Streaming.swift */,
152− 09AFAB031F159C9200EBD200 /* StreamingService.swift */,
153− 09F03F221F164E7E000CE916 /* StreamingParser.swift */,
154195 09F03F351F1674ED000CE916 /* VKServer.swift */,
155− 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */,
156− 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */,
157− 0919AEA81F0D459600894FFA /* CardView.swift */,
158− 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */,
159− 09BF42551F08413000AFAD57 /* MainViewController.swift */,
160− 09939A551F097DA90014719D /* TableViewCells.swift */,
161196 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */,
162197 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */,
163198 09BF42171F08352E00AFAD57 /* VKData.swift */,
@@ -270,6 +305,7 @@
270305 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */,
271306 09BF42101F0833C100AFAD57 /* MainCoordinator.swift in Sources */,
272307 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */,
308+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */,
273309 09BF42131F08341B00AFAD57 /* MyWeirdness.swift in Sources */,
274310 0919AEA91F0D459600894FFA /* CardView.swift in Sources */,
275311 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */,
Melk.xcodeproj/xcuserdata/ilya.xcuserdatad/xcschemes/Melk.xcscheme+3−3
@@ -42,9 +42,9 @@
4242 </AdditionalOptions>
4343 </TestAction>
4444 <LaunchAction
45− buildConfiguration = "Debug"
46− selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
47− selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
45+ buildConfiguration = "Release"
46+ selectedDebuggerIdentifier = ""
47+ selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
4848 launchStyle = "0"
4949 useCustomWorkingDirectory = "NO"
5050 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−10
@@ -12,20 +12,51 @@ class CardView: UIView {
1212
1313 var post: WallPost? {
1414 didSet {
15+// print(post?.owner)
1516 update()
1617 }
1718 }
19+ var minWidth: CGFloat = 300 {
20+ didSet {
21+// update()
22+ }
23+ }
24+ var height: CGFloat = 400 {
25+ didSet {
26+// update()
27+ }
28+ }
1829
1930 override init(frame: CGRect) {
2031 super.init(frame: frame)
2132 layer.masksToBounds = true
2233 layer.cornerRadius = 20
23− backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
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)
38+// backgroundColor = #colorLiteral(red: 0.9638966278, green: 0.9734401588, blue: 0.9734401588, alpha: 1)
2439 }
2540
2641 required init?(coder aDecoder: NSCoder) {
2742 fatalError("init(coder:) has not been implemented")
2843 }
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+ }
2960
3061// private let avatar = UIImage()
3162// private let name = UIImage()
@@ -34,14 +65,13 @@ class CardView: UIView {
3465 subviews.map {$0.removeFromSuperview()}
3566
3667 let largeStack = StackView(axis: .vertical, alignment: .left)
37− largeStack.padding = CGPoint(x: 32, y: 32)
68+ largeStack.padding = padding
3869 addSubview(largeStack)
3970
4071 let userView = StackView(axis: .horizontal, alignment: .center)
41−// userView.backgroundColor = #colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)
4272
4373
44− let avatarDim: CGFloat = 50
74+ let avatarDim: CGFloat = 64
4575 let avatar = UIImageView()
4676 post?.owner?.avatar?.load(into: avatar)
4777 avatar.layer.cornerRadius = avatarDim/2
@@ -51,30 +81,116 @@ class CardView: UIView {
5181 avatar.frame.size = CGSize(width: avatarDim, height: avatarDim)
5282 userView.addArrangedSubview(.view(avatar))
5383
54− userView.addArrangedSubview(.spacer(8))
84+ userView.addArrangedSubview(.spacer(16))
5585
5686 let userName = UILabel()
5787 let firstName = post?.owner?.firstName ?? ""
5888 let lastName = post?.owner?.lastName ?? ""
5989 userName.text = "\(firstName) \(lastName)"
6090 userName.font = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy)
91+ userName.textColor = UIColor.darkText
6192 userName.sizeToFit()
6293 userView.addArrangedSubview(.view(userName))
63− userView.frame.origin = CGPoint(x: 16, y: 16)
94+
95+ userView.layoutIfNeeded()
6496
6597 largeStack.addArrangedSubview(.view(userView))
6698
67− 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)
68153
69154 let body = UILabel()
70155 body.numberOfLines = 0
71− let bodyWidth: Double = 300
156+ let bodyWidth: Double = Double(width)
72157 body.font = UIFont.systemFont(ofSize: 22, weight: UIFontWeightSemibold)
73− 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
74160 let size = body.textRect(forBounds: CGRect(x: 0, y: 0, width: bodyWidth, height: Double.infinity), limitedToNumberOfLines: 0).size
75− 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+ }
76178
77179 largeStack.addArrangedSubview(.view(body))
78180
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+
79195 }
80196}
Melk/CollectionView.swift+4−2
@@ -68,7 +68,7 @@ class CollectionView: UIView {
6868 var firstStack = true
6969
7070 for view in arrangedSubviews {
71− print("new view")
71+// print("new view")
7272 if firstView {
7373 lastStack.addArrangedSubview(.view(view))
7474 firstView = false
@@ -94,7 +94,9 @@ class CollectionView: UIView {
9494 }
9595 }
9696 lastStack.layoutIfNeeded()
97− largeStack.addArrangedSubview(.spacer(verticalPadding))
97+ if !firstStack {
98+ largeStack.addArrangedSubview(.spacer(verticalPadding))
99+ }
98100 largeStack.addArrangedSubview(.view(lastStack))
99101 largeStack.frame.origin = CGPoint.zero
100102 largeStack.layoutIfNeeded()
Melk/MainCoordinator.swift+9−2
@@ -27,8 +27,17 @@ class MainCoordinator {
2727 deleteRealmIfMigrationNeeded: true)
2828
2929 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+ }
3038 updateScreens()
3139 stream.handleResult = { result in
40+ self.secondaryCoord.add(result: result)
3241// switch result {
3342// case .post(let post):
3443//
@@ -43,8 +52,6 @@ class MainCoordinator {
4352// }
4453 print("didAuthorize")
4554 window.rootViewController = navC
46− stream.rules = ["я", "а"]
47− stream.connect()
4855 }
4956
5057 func updateScreens() {
Melk/MainViewController.swift+22−2
@@ -29,7 +29,9 @@ class MainViewController: UIViewController {
2929 }
3030
3131 @objc private func presentTags() {
32−
32+ present?(
33+ mainView.tags.flatMap({$0}).filter({$0 != ""})
34+ )
3335 }
3436}
3537
@@ -45,6 +47,8 @@ class MainView: UIView {
4547 tableView.rowHeight = UITableViewAutomaticDimension
4648 tableView.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
4749 tableView.register(TagTableViewCell.self, forCellReuseIdentifier: TagTableViewCell.identifier)
50+ tableView.separatorStyle = .none
51+ tableView.allowsSelection = false
4852 setNeedsLayout()
4953 }
5054
@@ -52,6 +56,17 @@ class MainView: UIView {
5256 tableView.frame = bounds
5357 }
5458
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+
5570 required init?(coder aDecoder: NSCoder) {
5671 fatalError("init(coder:) has not been implemented")
5772 }
@@ -62,13 +77,18 @@ extension MainView: UITableViewDataSource {
6277 if let cell = tableView.dequeueReusableCell(withIdentifier: TagTableViewCell.identifier) as? TagTableViewCell {
6378// cell.titleText = "Testing testing 1 2 3"
6479 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]
6585 return cell
6686 }
6787 return UITableViewCell()
6888 }
6989
7090 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
71− return 3
91+ return tags.count
7292 }
7393
7494 func numberOfSections(in tableView: UITableView) -> Int {
Melk/SecondaryCoordinator.swift+24−1
@@ -12,19 +12,42 @@ class SecondaryCoordinator {
1212
1313 var windows: [UIWindow] = []
1414
15+ private var didStartPresenting = false
16+ private var tag: String = ""
17+
18+ func add(result: StreamingServiceResult) {
19+ for vc in VCs {
20+ vc.add(result: result)
21+ }
22+ }
23+
24+ func present(tag newTag: String) {
25+ didStartPresenting = true
26+ self.tag = newTag
27+ VCs.map({ $0.present(tag: newTag) })
28+ }
29+
1530 init() {
1631
1732 }
1833
34+ private var VCs: [SecondaryViewController] = []
35+
1936 func updateScreens() {
2037 windows.removeAll()
38+ VCs.removeAll()
2139 for screen in UIScreen.screens.dropFirst() {
2240 let window = UIWindow(frame: screen.bounds)
2341 window.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
2442 window.screen = screen
2543 window.isHidden = false
26− window.rootViewController = SecondaryViewController()
44+ let VC = SecondaryViewController()
45+ VCs.append(VC)
46+ window.rootViewController = VC
2747 windows.append(window)
48+ if didStartPresenting {
49+ VC.present(tag: tag)
50+ }
2851 }
2952 }
3053}
Melk/SecondaryViewController.swift+125−9
@@ -9,29 +9,145 @@
99import UIKit
1010
1111class SecondaryViewController: UIViewController {
12+
1213 init() {
1314 super.init(nibName: nil, bundle: nil)
14− self.view = SecondaryView(frame: view.bounds)
15+ self.view = sView
16+ }
17+
18+ func add(result: StreamingServiceResult) {
19+ sView.add(result: result)
20+ }
21+
22+ func present(tag: String) {
23+ sView.present(tag: tag)
1524 }
1625
1726 required init?(coder aDecoder: NSCoder) {
1827 fatalError("init(coder:) has not been implemented")
1928 }
29+
30+ private let sView = SecondaryView()
2031}
2132
2233class 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+
61+ func add(result: StreamingServiceResult) {
62+ switch result {
63+ case .post(let post):
64+ let card = CardContainer()
65+ card.minWidth = min(bounds.width / 2.3, 550)
66+ card.height = cardHeight
67+ card.post = post
68+ cards.addArrangedSubview(.spacer(spacer))
69+ cards.addArrangedSubview(.view(card))
70+ card.animate()
71+ }
72+ self.layoutIfNeeded(animated: true)
73+// self.setNeedsLayout(animated: true) // Doesn't work for some reason
74+ }
75+
76+// private let card = CardView()
77+ private let cards = StackView(axis: .horizontal, alignment: .top)
78+
2379 override init(frame: CGRect) {
2480 super.init(frame: frame)
81+ backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
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()
25128
26− let card = CardView(frame: CGRect(x: 20, y: 20, width: 600, height: 300))
27− let post = WallPost()
28− post.owner = User()
29− post.owner?.firstName = "David"
30− post.owner?.avatar = RealmPhoto()
31− post.owner?.avatar?.imageSURL = "https://static.independent.co.uk/s3fs-public/styles/article_small/public/thumbnails/image/2017/04/01/17/twitter-egg.jpg"
32− card.post = post
33− addSubview(card)
129+ melkLabel.sizeToFit()
130+ melkLabel.center = center
34131
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
35151 }
36152
37153 required init?(coder aDecoder: NSCoder) {
Melk/StackView.swift+3−1
@@ -44,6 +44,8 @@ class StackView: UIView {
4444 super.layoutSubviews()
4545 print("laying out subviews")
4646
47+ subviews.map({$0.removeFromSuperview()}) // TODO: There must be a better way!
48+
4749 var height: CGFloat = 0
4850 var width: CGFloat = 0
4951 switch axis {
@@ -62,7 +64,7 @@ class StackView: UIView {
6264 height += a
6365 }
6466 case .view(let view):
65−
67+ view.layoutIfNeeded()
6668 switch axis {
6769 case .horizontal:
6870 view.frame.origin.x = width
Melk/StreamingParser.swift+24−21
@@ -21,28 +21,23 @@ class StreamingParser {
2121 let postID: WallPostID = try eventID => "post_id"
2222 let ownerID: UserID = try eventID => "post_owner_id"
2323 // let
24+ var post: WallPost!
25+ var comp = 2
2426 realmWrite { _ in
25− let post = WallPost.by(id: postID)
27+ post = WallPost.by(id: postID)
2628 post.owner = User.by(id: ownerID)
27− var comp = 2
28− let complete = {
29− comp -= 1
30− if comp == 0 {
31− completion(.post(post))
32− }
33− }
3429 switch eventType {
3530 case "post":
3631 break
3732 case "comment":
3833 /*
39− if let id: Int = try? eventID => "comment_id" {
40− let comment = WallPostReply.by(id: id)
41− comment.wallPost = post
42− comp += 1
43− comment // TODO: get comment
44− }
45− */
34+ if let id: Int = try? eventID => "comment_id" {
35+ let comment = WallPostReply.by(id: id)
36+ comment.wallPost = post
37+ comp += 1
38+ comment // TODO: get comment
39+ }
40+ */
4641 print("comment not implemented")
4742
4843 case "share":
@@ -50,13 +45,21 @@ class StreamingParser {
5045 default:
5146 print("error:\n\(json)")
5247 }
53− post.owner?.get(completion: { (_) in
54− complete()
55− })
56− post.get(completion: { (_) in
57− complete()
58− })
5948 }
49+// print(post.owner)
50+ let complete = {
51+ comp -= 1
52+ if comp == 0 {
53+// print(post.owner)
54+ completion(.post(post))
55+ }
56+ }
57+ post.owner?.get(completion: { (_) in
58+ complete()
59+ })
60+ post.get(completion: { (_) in
61+ complete()
62+ })
6063 break // handle event
6164 case 300:
6265 break // handle service message
Melk/TableViewCells.swift+12−3
@@ -57,6 +57,12 @@ class TagTableViewCell: UITableViewCell {
5757 }
5858 }
5959
60+ var tagDidChange: ((String?) -> ())?
61+
62+ @objc private func textDidChange() {
63+ tagDidChange?(tagString)
64+ }
65+
6066 var style: TagStyle = .normal {
6167 didSet {
6268 updateStyle()
@@ -70,6 +76,9 @@ class TagTableViewCell: UITableViewCell {
7076
7177 textField.borderStyle = .roundedRect
7278 textField.adjustsFontSizeToFitWidth = false
79+ textField.autocapitalizationType = .none
80+ textField.autocorrectionType = .no
81+ textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged)
7382
7483 contentView.addSubview(textField)
7584// textField.delegate = self
@@ -92,7 +101,7 @@ class TagTableViewCell: UITableViewCell {
92101 CGRect.infinite, limitedToNumberOfLines: 1)
93102 .height + 2*2
94103
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)
96105 textField.frame = frame
97106 print(textField.frame)
98107 }
@@ -103,8 +112,8 @@ class TagTableViewCell: UITableViewCell {
103112
104113
105114 static let identifier = "io.Melk.tagTableViewCell"
106− static let largeHeight: CGFloat = 37.5 + 8*2
107− static let normalHeight: CGFloat = 28.0 + 8*2
115+ static let largeHeight: CGFloat = 37.5 + 16 + 32
116+ static let normalHeight: CGFloat = 28.0 + 4*2
108117}
109118
110119//extension TagTableViewCell: UITextFieldDelegate {
Melk/VKData.swift+9−9
@@ -49,7 +49,7 @@ class VKData {
4949 }
5050 user.birthday = formatter.date(from: birthday)
5151 }
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"
5353 user.photo = RealmPhoto.by(user.photoID, surl: json["photo_max_orig"].string)
5454 if let friendStat = json["friend_status"].int {
5555 user.friendStatus = FriendStatus(rawValue: friendStat)
@@ -289,13 +289,13 @@ class VKData {
289289 photo.width.value = json["width"].int
290290 photo.height.value = json["height"].int
291291 var surl: String?
292− if let imageSurl = json["photo_2560"].string {
293− surl = imageSurl
294− } else if let imageSurl = json["photo_1280"].string {
295− surl = imageSurl
296− } else if let imageSurl = json["photo_807"].string {
297− surl = imageSurl
298− } else if let imageSurl = json["photo_604"].string {
292+// if let imageSurl = json["photo_2560"].string {
293+// surl = imageSurl
294+// } else if let imageSurl = json["photo_1280"].string {
295+// surl = imageSurl
296+// } else if let imageSurl = json["photo_807"].string {
297+// surl = imageSurl
298+ /*} else*/ if let imageSurl = json["photo_604"].string {
299299 surl = imageSurl
300300 } else if let imageSurl = json["photo_130"].string {
301301 surl = imageSurl
@@ -430,7 +430,7 @@ class VKData {
430430 var post: WallPost!
431431 realmWrite { _ in
432432 post = WallPost.by(id: id)
433− post.owner = json["to_id"].int >>> {User.by(id: $0)}
433+ post.owner = json["owner_id"].int >>> {User.by(id: $0)}
434434 post.creator = json["from_id"].int >>> {User.by(id: $0)}
435435 post.date = json["date"].int >>> {Date.init(timeIntervalSince1970: TimeInterval($0))}
436436 post.body = json["text"].string
Melk/VKServer.swift+3−2
@@ -80,7 +80,8 @@ class VKServer {
8080 static func get(user: User, completion: @escaping ((Data) -> ())) {
8181 let id = user.id
8282 let params: Parameters = [
83− "user_ids": id
83+ "user_ids": id,
84+ "fields": "photo_100"
8485 ]
8586 Alamofire.request(url(for: "users.get"), parameters: params)
8687 .validate().responseData { (response) in
@@ -117,6 +118,6 @@ class VKServer {
117118}
118119
119120fileprivate func url(`for` method: String) -> URL {
120− 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"
121122 return URL.init(string: surl)!
122123}
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”, записи с основным хештегом будут выводиться на любые подключенные к устройству экраны.