Initial avatars implementation
This commit is contained in:
parent
21c54c2759
commit
b5f3179a50
|
@ -9,6 +9,7 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
04DACE8A212CA6B7009840C4 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE89212CA6B7009840C4 /* Timeline.swift */; };
|
||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
||||
04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* AvatarCache.swift */; };
|
||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
||||
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; };
|
||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||
|
@ -63,6 +64,7 @@
|
|||
/* Begin PBXFileReference section */
|
||||
04DACE89212CA6B7009840C4 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
|
||||
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
||||
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarCache.swift; sourceTree = "<group>"; };
|
||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
||||
D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = "<group>"; };
|
||||
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -147,7 +149,9 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||
04DACE89212CA6B7009840C4 /* Timeline.swift */,
|
||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */,
|
||||
D6F953F121251A2F00CF0F2B /* Controllers */,
|
||||
D6F953E9212519B800CF0F2B /* View Controllers */,
|
||||
D6BED1722126661300F02DA0 /* Views */,
|
||||
|
@ -155,7 +159,6 @@
|
|||
D6D4DDD6212518A200E1C4BB /* Assets.xcassets */,
|
||||
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */,
|
||||
D6D4DDDB212518A200E1C4BB /* Info.plist */,
|
||||
04DACE89212CA6B7009840C4 /* Timeline.swift */,
|
||||
);
|
||||
path = Tusker;
|
||||
sourceTree = "<group>";
|
||||
|
@ -343,6 +346,7 @@
|
|||
04DACE8A212CA6B7009840C4 /* Timeline.swift in Sources */,
|
||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||
04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */,
|
||||
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */,
|
||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// ImageCache.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfactson 8/21/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class AvatarCache {
|
||||
|
||||
static let shared = AvatarCache()
|
||||
|
||||
let cache = NSCache<NSString, UIImage>()
|
||||
|
||||
var requests = [URL: URLSessionDataTask]()
|
||||
var requestCallbacks = [URL: [(UIImage?) -> Void]]()
|
||||
|
||||
|
||||
|
||||
private init() {
|
||||
}
|
||||
|
||||
func get(_ url: URL, completion: @escaping (UIImage?) -> Void) {
|
||||
let key = url.absoluteString as NSString
|
||||
if let image = cache.object(forKey: key) {
|
||||
completion(image)
|
||||
} else if var callbacks = requestCallbacks[url] {
|
||||
callbacks.append(completion)
|
||||
requestCallbacks[url] = callbacks
|
||||
} else {
|
||||
requestCallbacks[url] = [completion]
|
||||
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
guard error == nil,
|
||||
let data = data,
|
||||
let image = UIImage(data: data) else {
|
||||
let callbacks = self.requestCallbacks.removeValue(forKey: url)!
|
||||
for callback in callbacks {
|
||||
// todo: default avatar for failed requests
|
||||
callback(nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let callbacks = self.requestCallbacks.removeValue(forKey: url)!
|
||||
for callback in callbacks {
|
||||
callback(image)
|
||||
}
|
||||
self.cache.setObject(image, forKey: key)
|
||||
}
|
||||
task.resume()
|
||||
requests[url] = task
|
||||
}
|
||||
}
|
||||
|
||||
func cancel(_ url: URL) {
|
||||
requests[url]?.cancel()
|
||||
requests.removeValue(forKey: url)
|
||||
requestCallbacks.removeValue(forKey: url)
|
||||
}
|
||||
|
||||
}
|
|
@ -24,37 +24,48 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="375" height="71.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZFA-wR-9b4">
|
||||
<rect key="frame" x="16" y="11" width="103" height="21"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZFA-wR-9b4">
|
||||
<rect key="frame" x="76" y="11" width="103" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="@Username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ffn-MC-jPW">
|
||||
<rect key="frame" x="127" y="11" width="94" height="21"/>
|
||||
<rect key="frame" x="187" y="11" width="172" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xX6-Gq-MFH">
|
||||
<rect key="frame" x="16" y="40" width="343" height="21"/>
|
||||
<rect key="frame" x="76" y="40" width="283" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="7mj-Ts-blf">
|
||||
<rect key="frame" x="16" y="11" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="7mj-Ts-blf" secondAttribute="height" multiplier="1:1" id="9EB-YI-W3F"/>
|
||||
<constraint firstAttribute="width" constant="50" id="Zon-62-0O6"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="xX6-Gq-MFH" firstAttribute="trailing" secondItem="JcO-MN-o4M" secondAttribute="trailingMargin" id="3uJ-2V-odU"/>
|
||||
<constraint firstItem="Ffn-MC-jPW" firstAttribute="leading" secondItem="ZFA-wR-9b4" secondAttribute="trailing" constant="8" id="Az0-iw-JrZ"/>
|
||||
<constraint firstItem="Ffn-MC-jPW" firstAttribute="top" secondItem="JcO-MN-o4M" secondAttribute="topMargin" id="I7N-O4-Q6U"/>
|
||||
<constraint firstItem="ZFA-wR-9b4" firstAttribute="leading" secondItem="JcO-MN-o4M" secondAttribute="leadingMargin" id="OGD-sl-OQI"/>
|
||||
<constraint firstItem="ZFA-wR-9b4" firstAttribute="leading" secondItem="7mj-Ts-blf" secondAttribute="trailing" constant="10" id="QJG-4Q-OfA"/>
|
||||
<constraint firstItem="xX6-Gq-MFH" firstAttribute="bottom" secondItem="JcO-MN-o4M" secondAttribute="bottomMargin" id="Td8-7G-sJ7"/>
|
||||
<constraint firstItem="ZFA-wR-9b4" firstAttribute="top" secondItem="JcO-MN-o4M" secondAttribute="topMargin" id="a0F-RV-jI0"/>
|
||||
<constraint firstItem="xX6-Gq-MFH" firstAttribute="leading" secondItem="JcO-MN-o4M" secondAttribute="leadingMargin" id="oSD-4i-vYB"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Ffn-MC-jPW" secondAttribute="trailing" id="eNm-X8-RAi"/>
|
||||
<constraint firstItem="xX6-Gq-MFH" firstAttribute="leading" secondItem="7mj-Ts-blf" secondAttribute="trailing" constant="10" id="f2g-6V-rMH"/>
|
||||
<constraint firstItem="7mj-Ts-blf" firstAttribute="top" secondItem="JcO-MN-o4M" secondAttribute="topMargin" id="qBF-aB-ko3"/>
|
||||
<constraint firstItem="7mj-Ts-blf" firstAttribute="leading" secondItem="JcO-MN-o4M" secondAttribute="leadingMargin" id="tna-7N-d7r"/>
|
||||
<constraint firstItem="xX6-Gq-MFH" firstAttribute="top" secondItem="ZFA-wR-9b4" secondAttribute="bottom" constant="8" id="ufm-zI-r2q"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="avatarImageView" destination="7mj-Ts-blf" id="t77-xO-FIM"/>
|
||||
<outlet property="contentLabel" destination="xX6-Gq-MFH" id="d7X-0B-daj"/>
|
||||
<outlet property="displayNameLabel" destination="ZFA-wR-9b4" id="4Bg-aE-tVd"/>
|
||||
<outlet property="usernameLabel" destination="Ffn-MC-jPW" id="dfo-KY-c7S"/>
|
||||
|
|
|
@ -15,9 +15,12 @@ class StatusTableViewCell: UITableViewCell {
|
|||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var contentLabel: UILabel!
|
||||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
|
||||
var status: Status!
|
||||
|
||||
var avatarURL: URL?
|
||||
|
||||
var layoutManager: NSLayoutManager!
|
||||
var textContainer: NSTextContainer!
|
||||
var textStorage: NSTextStorage!
|
||||
|
@ -35,6 +38,17 @@ class StatusTableViewCell: UITableViewCell {
|
|||
}
|
||||
displayNameLabel.text = account.displayName
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
avatarImageView.layer.cornerRadius = 5
|
||||
avatarImageView.layer.masksToBounds = true
|
||||
avatarImageView.image = nil
|
||||
if let url = URL(string: account.avatar) {
|
||||
AvatarCache.shared.get(url) { image in
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let doc = try! SwiftSoup.parse(status.content)
|
||||
let body = doc.body()!
|
||||
|
@ -114,5 +128,11 @@ class StatusTableViewCell: UITableViewCell {
|
|||
print("Open URL: \(url)")
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
if let url = avatarURL {
|
||||
AvatarCache.shared.cancel(url)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue